blob: a4e01acbcec754e1af6a1aa1d93cfbc565c6b9b0 [file] [log] [blame]
<!--
Copyright (c) 2008,2020 Silicon Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<div class="col q-px-lg">
<div class="row q-py-md text-h5">
<span class="v-step-6">
Endpoint
{{ this.endpointId[this.selectedEndpointId] }}
Device Type Features
</span>
</div>
<div class="col column linear-border-wrap">
<div v-if="deviceTypeFeatures.length > 0">
<q-table
class="my-striped-table"
:rows="deviceTypeFeatures"
:columns="columns"
flat
v-model:pagination="pagination"
separator="horizontal"
id="ZclDeviceTypeFeatureManager"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="enabled" :props="props" auto-width>
<q-toggle
:disable="isToggleDisabled(props.row.conformance)"
class="q-mt-xs v-step-14"
v-model="enabledDeviceTypeFeatures"
:val="
hashDeviceTypeClusterIdFeatureId(
props.row.deviceTypeClusterId,
props.row.featureId
)
"
indeterminate-value="false"
keep-color
@update:model-value="
(val) => {
onToggleDeviceTypeFeature(props.row, val)
}
"
/>
</q-td>
<q-td key="deviceType" :props="props" auto-width>
<div
v-for="deviceType in props.row.deviceTypes"
:key="deviceType"
>
{{ deviceType }}
</div>
</q-td>
<q-td key="cluster" :props="props" auto-width>
{{ props.row.cluster }}
</q-td>
<q-td key="clusterSide" :props="props" auto-width>
{{ getClusterSide(props.row) }}
</q-td>
<q-td key="featureName" :props="props" auto-width>
{{ props.row.name }}
</q-td>
<q-td key="code" :props="props" auto-width>
{{ props.row.code }}
</q-td>
<q-td key="conformance" :props="props" auto-width>
{{ props.row.conformance }}
</q-td>
<q-td key="bit" :props="props" auto-width>
{{ props.row.bit }}
</q-td>
<q-td key="description" :props="props" auto-width>
{{ props.row.description }}
</q-td>
</q-tr>
</template>
</q-table>
<q-dialog v-model="showDialog">
<q-card>
<q-card-section>
<div class="row items-center">
<div class="text-h6 col">Updated Elements</div>
<div class="col-1 text-right">
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip>Close</q-tooltip>
</q-btn>
</div>
</div>
<div v-if="attributesToUpdate.length > 0">
<div
class="text-body1"
style="margin-top: 15px; padding-left: 20px"
>
Attributes
</div>
<ul>
<li
v-for="(attribute, index) in attributesToUpdate"
:key="'attribute' + index"
style="margin-bottom: 10px"
>
{{ attribute }}
</li>
</ul>
</div>
<div v-if="commandsToUpdate.length > 0">
<div
class="text-body1"
style="margin-top: 15px; padding-left: 20px"
>
Commands
</div>
<ul>
<li
v-for="(command, index) in commandsToUpdate"
:key="'command' + index"
style="margin-bottom: 10px"
>
{{ command }}
</li>
</ul>
</div>
<div v-if="eventsToUpdate.length > 0">
<div
class="text-body1"
style="margin-top: 15px; padding-left: 20px"
>
Events
</div>
<ul>
<li
v-for="(event, index) in eventsToUpdate"
:key="'event' + index"
style="margin-bottom: 10px"
>
{{ event }}
</li>
</ul>
</div>
<div v-if="noElementsToUpdate">
<div class="text-body1" style="margin-top: 15px">
{{ noElementsToUpdateMessage }}
</div>
</div>
</q-card-section>
</q-card>
</q-dialog>
</div>
<div v-else class="q-pa-md">
<div class="text-body1">
{{ noDataMessage }}
</div>
</div>
</div>
</div>
</template>
<script>
import CommonMixin from '../util/common-mixin'
import EditableAttributeMixin from '../util/editable-attributes-mixin'
import dbEnum from '../../src-shared/db-enum'
import restApi from '../../src-shared/rest-api'
import { Notify } from 'quasar'
export default {
name: 'ZclDeviceTypeFeatureManager',
mixins: [CommonMixin, EditableAttributeMixin],
methods: {
getClusterSide(row) {
if (row.includeClient == 1 && row.includeServer == 1) {
return 'Client & Server'
} else if (row.includeClient == 1) {
return 'Client'
} else if (row.includeServer == 1) {
return 'Server'
} else {
return 'none'
}
},
isToggleDisabled(conformance) {
// disable toggling unsupported features
return conformance == 'X' || conformance == 'D'
},
onToggleDeviceTypeFeature(featureData, inclusionList) {
/* when conformance is not properly handled and change is disabled,
do not set featureMap attribute */
let disabled = this.updateElementsAndSetWarnings(
featureData,
inclusionList
)
if (!disabled) {
this.setFeatureMapAttribute(featureData)
}
},
setFeatureMapAttribute(featureData) {
let featureMapAttributeId = featureData.featureMapAttributeId
let bit = featureData.bit
let featureMapValue = featureData.featureMapValue
let newValue = parseInt(featureMapValue) ^ (1 << bit)
newValue = newValue.toString()
this.$serverPatch(restApi.uri.updateBitOfFeatureMapAttribute, {
featureMapAttributeId: featureMapAttributeId,
newValue: newValue
})
this.$store.commit('zap/updateFeatureMapAttributeOfFeature', {
featureMapAttributeId: featureMapAttributeId,
featureMapValue: newValue
})
},
updateElementsAndSetWarnings(featureData, inclusionList) {
let featureMap = {}
this.deviceTypeFeatures.forEach((feature) => {
if (feature.deviceTypeClusterId == featureData.deviceTypeClusterId) {
let enabled = this.featureIsEnabled(feature, inclusionList)
featureMap[feature.code] = enabled ? 1 : 0
}
})
this.$serverPost(restApi.uri.checkConformOnFeatureUpdate, {
featureData: featureData,
featureMap: featureMap,
endpointId: this.endpointId[this.selectedEndpointId]
}).then((res) => {
let {
attributesToUpdate,
commandsToUpdate,
eventsToUpdate,
displayWarning,
warningMessage,
disableChange
} = res.data
// show popup warning message
if (displayWarning) {
if (!Array.isArray(warningMessage)) {
warningMessage = [warningMessage]
}
for (let message of warningMessage) {
Notify.create({
message: message,
type: 'warning',
classes: 'custom-notification notification-warning',
position: 'top',
html: true
})
}
}
// if disableChange is true, the case is too complex to handle
// throw warnings and skip the following actions
if (disableChange) {
return disableChange
}
this.$store
.dispatch('zap/setRequiredElements', {
featureMap: featureMap,
deviceTypeClusterId: featureData.deviceTypeClusterId,
endpointTypeClusterId: featureData.endpointTypeClusterId
})
.then(() => {
// toggle attributes, commands, and events for correct conformance,
// and set their conformance notifications
attributesToUpdate.forEach((attribute) => {
let editContext = {
action: 'boolean',
endpointTypeIdList: this.endpointTypeIdList,
selectedEndpoint: this.selectedEndpointTypeId,
id: attribute.id,
value: attribute.value,
listType: 'selectedAttributes',
clusterRef: attribute.clusterRef,
attributeSide: attribute.side,
reportMinInterval: attribute.reportMinInterval,
reportMaxInterval: attribute.reportMaxInterval
}
this.setRequiredElementNotifications(
attribute,
attribute.value,
'attributes'
)
this.$store.dispatch('zap/updateSelectedAttribute', editContext)
})
commandsToUpdate.forEach((command) => {
let listType =
command.source == 'client' ? 'selectedIn' : 'selectedOut'
let editContext = {
action: 'boolean',
endpointTypeIdList: this.endpointTypeIdList,
id: command.id,
value: command.value,
listType: listType,
clusterRef: command.clusterRef,
commandSide: command.source
}
this.setRequiredElementNotifications(
command,
command.value,
'commands'
)
this.$store.dispatch('zap/updateSelectedCommands', editContext)
})
eventsToUpdate.forEach((event) => {
let editContext = {
action: 'boolean',
endpointTypeId: this.selectedEndpointTypeId,
id: event.id,
value: event.value,
listType: 'selectedEvents',
clusterRef: event.clusterRef,
eventSide: event.side
}
this.setRequiredElementNotifications(event, event.value, 'events')
this.$store.dispatch('zap/updateSelectedEvents', editContext)
})
// prepare messages and show dialog
this.attributesToUpdate = attributesToUpdate
.map((attribute) =>
attribute.value
? 'enabled ' + attribute.name
: 'disabled ' + attribute.name
)
.sort((a, b) => (a.includes('enabled') ? -1 : 1))
this.commandsToUpdate = commandsToUpdate
.map((command) =>
command.value
? 'enabled ' + command.name
: 'disabled ' + command.name
)
.sort((a, b) => (a.includes('enabled') ? -1 : 1))
this.eventsToUpdate = eventsToUpdate
.map((event) =>
event.value ? 'enabled ' + event.name : 'disabled ' + event.name
)
.sort((a, b) => (a.includes('enabled') ? -1 : 1))
this.showDialog = true
// update enabled device type features
let added = this.featureIsEnabled(featureData, inclusionList)
let hashedVal = this.hashDeviceTypeClusterIdFeatureId(
featureData.deviceTypeClusterId,
featureData.featureId
)
this.$store.commit('zap/updateInclusionList', {
id: hashedVal,
added: added,
listType: 'enabledDeviceTypeFeatures',
view: 'featureView'
})
})
return disableChange
})
},
featureIsEnabled(featureData, inclusionList) {
return inclusionList.includes(
this.hashDeviceTypeClusterIdFeatureId(
featureData.deviceTypeClusterId,
featureData.featureId
)
)
}
},
computed: {
noElementsToUpdate() {
return (
this.attributesToUpdate.length == 0 &&
this.commandsToUpdate.length == 0 &&
this.eventsToUpdate.length == 0
)
}
},
data() {
return {
noDataMessage: 'No device type features available for this endpoint',
noElementsToUpdateMessage:
'No elements need to be updated after toggling this feature',
pagination: {
rowsPerPage: 10
},
showDialog: false,
attributesToUpdate: [],
commandsToUpdate: [],
eventsToUpdate: [],
columns: [
{
name: dbEnum.feature.name.enabled,
required: true,
label: dbEnum.feature.label.enabled,
align: 'left'
},
{
name: dbEnum.feature.name.deviceType,
required: true,
label: dbEnum.feature.label.deviceType,
align: 'left'
},
{
name: dbEnum.feature.name.cluster,
required: true,
label: dbEnum.feature.label.cluster,
align: 'left'
},
{
name: dbEnum.feature.name.clusterSide,
required: true,
label: dbEnum.feature.label.clusterSide,
align: 'left'
},
{
name: dbEnum.feature.name.featureName,
required: true,
label: dbEnum.feature.label.featureName,
align: 'left'
},
{
name: dbEnum.feature.name.code,
required: true,
label: dbEnum.feature.label.code,
align: 'left'
},
{
name: dbEnum.feature.name.conformance,
required: true,
label: dbEnum.feature.label.conformance,
align: 'left'
},
{
name: dbEnum.feature.name.bit,
required: true,
label: dbEnum.feature.label.bit,
align: 'left'
},
{
name: dbEnum.feature.name.description,
required: false,
label: dbEnum.feature.label.description,
align: 'left'
}
]
}
}
}
</script>