forked from platypush/platypush
Z-Wave plugin UI migration [WIP]
This commit is contained in:
parent
add1bd05cb
commit
b49865181b
7 changed files with 1662 additions and 0 deletions
|
@ -29,6 +29,9 @@
|
||||||
},
|
},
|
||||||
"zigbee.mqtt": {
|
"zigbee.mqtt": {
|
||||||
"imgUrl": "/icons/zigbee.svg"
|
"imgUrl": "/icons/zigbee.svg"
|
||||||
|
},
|
||||||
|
"zwave": {
|
||||||
|
"imgUrl": "/icons/z-wave.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<div class="item group" :class="{selected: selected}">
|
||||||
|
<div class="row name vertical-center" :class="{selected: selected}" v-text="group.label"
|
||||||
|
@click="$emit('select', group.index)" />
|
||||||
|
|
||||||
|
<div class="params" v-if="selected">
|
||||||
|
<div class="section nodes">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title col-10">Nodes</div>
|
||||||
|
<div class="buttons col-2">
|
||||||
|
<button class="btn btn-default" title="Add to group" @click="$emit('open-add-nodes-to-group', group.index)"
|
||||||
|
v-if="!group.max_associations || Object.keys(nodes || {}).length < group.max_associations">
|
||||||
|
<i class="fa fa-plus" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row" v-for="(node, i) in nodes" :key="i">
|
||||||
|
<div class="col-10" v-text="node.name?.length ? node.name : `<Node ${node.node_id}>`" />
|
||||||
|
<div class="buttons col-2">
|
||||||
|
<button class="btn btn-default" title="Remove from group" :disabled="commandRunning"
|
||||||
|
@click="removeFromGroup(node.node_id)">
|
||||||
|
<i class="fa fa-trash" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section config">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Parameters</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Index</div>
|
||||||
|
<div class="param-value" v-text="group.index"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Max associations</div>
|
||||||
|
<div class="param-value" v-text="group.max_associations"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Group",
|
||||||
|
emits: ['select', 'open-add-nodes-to-group'],
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
group: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { return {} },
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
commandRunning: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async removeFromGroup(nodeId) {
|
||||||
|
if (!confirm('Are you sure that you want to remove this node from ' + this.group.label + '?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.remove_node_from_group', {
|
||||||
|
node_id: nodeId,
|
||||||
|
group_index: this.group.index,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "common";
|
||||||
|
</style>
|
|
@ -0,0 +1,723 @@
|
||||||
|
<template>
|
||||||
|
<div class="zwave-container">
|
||||||
|
<Modal title="Network info" ref="networkInfoModal">
|
||||||
|
<div class="network-info">
|
||||||
|
<Loading v-if="loading.status" />
|
||||||
|
|
||||||
|
<div class="params" v-else>
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">State</div>
|
||||||
|
<div class="param-value" v-text="status.state"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Device</div>
|
||||||
|
<div class="param-value" v-text="status.device"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Statistics</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row"
|
||||||
|
v-for="(value, name) in status.stats"
|
||||||
|
:key="name">
|
||||||
|
<div class="param-name" v-text="name"></div>
|
||||||
|
<div class="param-value" v-text="value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal title="Add nodes to group" ref="addNodesToGroupModal">
|
||||||
|
<div class="group-add">
|
||||||
|
<div class="params">
|
||||||
|
<div class="section">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Select nodes to add</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row clickable" @click="addToGroup(node.node_id, selected.groupId)" :key="node.node_id"
|
||||||
|
v-for="node in Object.values(nodes).filter((n) => groups[selected.groupId].associations.indexOf(n.node_id) < 0)">
|
||||||
|
<div class="param-name" v-text="node.name"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<div class="view-options">
|
||||||
|
<div class="view-selector col-s-9 col-m-10 col-l-11">
|
||||||
|
<label>
|
||||||
|
<select @change="selected.view = $event.target.value">
|
||||||
|
<option v-for="(id, view) in views"
|
||||||
|
v-text="(view[0].toUpperCase() + view.slice(1)).replace('_', ' ')"
|
||||||
|
:key="id"
|
||||||
|
:selected="view === selected.view"
|
||||||
|
:value="view">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn btn-default" title="Add node" v-if="selected.view === 'nodes'" @click="addNode"
|
||||||
|
:disabled="commandRunning">
|
||||||
|
<i class="fa fa-plus" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" title="Remove node" v-if="selected.view === 'nodes'" @click="removeNode"
|
||||||
|
:disabled="commandRunning">
|
||||||
|
<i class="fa fa-minus" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" title="Add scene" v-if="selected.view === 'scenes'" @click="addScene"
|
||||||
|
:disabled="commandRunning">
|
||||||
|
<i class="fa fa-plus" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" title="Network info" @click="networkInfoModalOpen">
|
||||||
|
<i class="fa fa-info" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Dropdown title="Network commands" icon-class="fa fa-cog">
|
||||||
|
<DropdownItem text="Start Network" :disabled="commandRunning" @click="startNetwork" />
|
||||||
|
<DropdownItem text="Stop Network" :disabled="commandRunning" @click="stopNetwork" />
|
||||||
|
<DropdownItem text="Switch All On" :disabled="commandRunning" @click="switchAll(true)" />
|
||||||
|
<DropdownItem text="Switch All Off" :disabled="commandRunning" @click="switchAll(false)" />
|
||||||
|
<DropdownItem text="Cancel Command" :disabled="commandRunning" @click="cancelCommand" />
|
||||||
|
<DropdownItem text="Kill Command" :disabled="commandRunning" @click="killCommand" />
|
||||||
|
<DropdownItem text="Receive Configuration" :disabled="commandRunning" @click="receiveConfiguration" />
|
||||||
|
<DropdownItem text="Create New Primary" :disabled="commandRunning" @click="createNewPrimary" />
|
||||||
|
<DropdownItem text="Transfer Primary Role" :disabled="commandRunning" @click="transferPrimaryRole" />
|
||||||
|
<DropdownItem text="Heal Network" :disabled="commandRunning" @click="healNetwork" />
|
||||||
|
<DropdownItem text="Soft Reset" :disabled="commandRunning" @click="softReset" />
|
||||||
|
<DropdownItem text="Hard Reset" :disabled="commandRunning" @click="hardReset" />
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
<button class="btn btn-default" title="Refresh network" @click="refresh">
|
||||||
|
<i class="fa fa-sync-alt" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view nodes" v-if="selected.view === 'nodes'">
|
||||||
|
<Loading v-if="loading.nodes" />
|
||||||
|
<div class="no-items" v-else-if="!Object.keys(nodes || {}).length">
|
||||||
|
<div class="empty">No nodes available on the network</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Node v-for="(node, nodeId) in nodes" :key="nodeId" :node="node" :selected="selected.nodeId === nodeId"
|
||||||
|
@select="onNodeClick(nodeId)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view groups" v-else-if="selected.view === 'groups'">
|
||||||
|
<Loading v-if="loading.groups" />
|
||||||
|
<div class="no-items" v-else-if="!Object.keys(groups || {}).length">
|
||||||
|
<div class="empty">No groups available on the network</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Group v-for="(group, groupId) in groups" :key="groupId" :group="group" :selected="selected.groupId === groupId"
|
||||||
|
:nodes="groupId in groups ? groups[groupId].associations.map((node) => nodes[node]).
|
||||||
|
reduce((nodes, node) => {nodes[node.node_id] = node; return nodes}, {}) : {}"
|
||||||
|
@select="selected.groupId = groupId === selected.groupId ? undefined : groupId"
|
||||||
|
@open-add-nodes-to-group="$refs.addNodesToGroupModal.show()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view scenes" v-else-if="selected.view === 'scenes'">
|
||||||
|
<Loading v-if="loading.scenes" />
|
||||||
|
<div class="no-items" v-else-if="!Object.keys(scenes || {}).length">
|
||||||
|
<div class="empty">No scenes configured on the network</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item scene" :class="{selected: selected.sceneId === sceneId}"
|
||||||
|
v-for="(scene, sceneId) in scenes" :key="sceneId">
|
||||||
|
<div class="row name vertical-center" :class="{selected: selected.sceneId === sceneId}" v-text="scene.label"
|
||||||
|
@click="selected.sceneId = sceneId === selected.sceneId ? undefined : sceneId" />
|
||||||
|
|
||||||
|
<div class="params" v-if="selected.sceneId === sceneId">
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Activate</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<ToggleSwitch :value="false" @input="activateScene(sceneId)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section actions">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row" @click="removeScene(sceneId)">
|
||||||
|
<div class="param-name">Remove Scene</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="renameScene(sceneId)">
|
||||||
|
<div class="param-name">Rename Scene</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-edit"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section values" v-if="scene.values?.length">
|
||||||
|
<div class="value-container"
|
||||||
|
v-if="value.id_on_network && value.id_on_network in scenes.values[sceneId]" :scenes="scenes">
|
||||||
|
<Value v-for="(value, valueId) in valuesMap" :key="valueId" :value="value" :node="node" :sceneId="sceneId"
|
||||||
|
@add-to-scene="addValueToScene" @remove-from-scene="removeValueFromScene" @refresh="refreshNodes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view values" v-else>
|
||||||
|
<Loading v-if="loading.nodes" />
|
||||||
|
<div class="no-items" v-else-if="!Object.keys(nodes || {}).length">
|
||||||
|
<div class="empty">No nodes found on the network</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="node-container"
|
||||||
|
v-if="selected.view === 'values' || Object.values(node.values).filter((value) => value.id_on_network in values[selected.view]).length > 0">
|
||||||
|
<div class="item node"
|
||||||
|
:class="{selected: selected.nodeId === nodeId}"
|
||||||
|
v-for="(node, nodeId) in nodes"
|
||||||
|
:key="nodeId">
|
||||||
|
<div class="row name vertical-center" :class="{selected: selected.nodeId === nodeId}" v-text="node.name"
|
||||||
|
@click="onNodeClick(nodeId)"></div>
|
||||||
|
|
||||||
|
<div class="params" v-if="selected.nodeId === nodeId">
|
||||||
|
<div class="value-container"
|
||||||
|
v-if="value.id_on_network && (selected.view === 'values' || value.id_on_network in values[selected.view])">
|
||||||
|
<Value v-for="(value, valueId) in node.values" :key="valueId" :value="value" :node="node" :scenes="scenes"
|
||||||
|
@add-to-scene="addValueToScene" @remove-from-scene="removeValueFromScene" @refresh="refreshNodes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Group from "@/components/panels/Zwave/Group";
|
||||||
|
import Node from "@/components/panels/Zwave/Node";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
|
import Value from "@/components/panels/Zwave/Value";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Zwave",
|
||||||
|
components: {Value, ToggleSwitch, Loading, DropdownItem, Dropdown, Modal, Group, Node},
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
status: {},
|
||||||
|
views: {},
|
||||||
|
nodes: {},
|
||||||
|
groups: {},
|
||||||
|
scenes: {},
|
||||||
|
commandRunning: false,
|
||||||
|
values: {
|
||||||
|
switches: {},
|
||||||
|
dimmers: {},
|
||||||
|
sensors: {},
|
||||||
|
battery_levels: {},
|
||||||
|
power_levels: {},
|
||||||
|
bulbs: {},
|
||||||
|
doorlocks: {},
|
||||||
|
usercodes: {},
|
||||||
|
thermostats: {},
|
||||||
|
protections: {},
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
view: 'nodes',
|
||||||
|
nodeId: undefined,
|
||||||
|
groupId: undefined,
|
||||||
|
sceneId: undefined,
|
||||||
|
valueId: undefined,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
status: false,
|
||||||
|
nodes: false,
|
||||||
|
groups: false,
|
||||||
|
scenes: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
valuesMap() {
|
||||||
|
const values = {}
|
||||||
|
for (const node of Object.values(this.nodes)) {
|
||||||
|
for (const value of Object.values(node.values)) {
|
||||||
|
values[value.id_on_network] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async refreshNodes() {
|
||||||
|
this.loading.nodes = true
|
||||||
|
try {
|
||||||
|
this.nodes = await this.request('zwave.get_nodes')
|
||||||
|
} finally {
|
||||||
|
this.loading.nodes = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.nodes || {}).length)
|
||||||
|
this.views.values = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshGroups() {
|
||||||
|
this.loading.groups = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.groups = Object.values(await this.request('zwave.get_groups'))
|
||||||
|
.filter((group) => group.index)
|
||||||
|
.reduce((groups, group) => {
|
||||||
|
groups[group.index] = group
|
||||||
|
return groups
|
||||||
|
}, {})
|
||||||
|
} finally {
|
||||||
|
this.loading.groups = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.groups || {}).length)
|
||||||
|
this.views.groups = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshScenes() {
|
||||||
|
this.loading.scenes = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.scenes = Object.values(await this.request('zwave.get_scenes'))
|
||||||
|
.filter((scene) => scene.scene_id)
|
||||||
|
.reduce((scenes, scene) => {
|
||||||
|
scenes[scene.scene_id] = scene
|
||||||
|
return scenes
|
||||||
|
}, {})
|
||||||
|
} finally {
|
||||||
|
this.loading.scenes = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.scenes || {}).length)
|
||||||
|
this.views.values = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshValues(type) {
|
||||||
|
this.loading.values = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.values[type] = Object.values(await this.request('zwave.get_' + type))
|
||||||
|
.filter((item) => item.id_on_network)
|
||||||
|
.reduce((values, value) => {
|
||||||
|
values[value.id_on_network] = true
|
||||||
|
return values
|
||||||
|
}, {})
|
||||||
|
} finally {
|
||||||
|
this.loading.values = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.values[type]).length)
|
||||||
|
this.views[type] = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshStatus() {
|
||||||
|
this.loading.status = true
|
||||||
|
try {
|
||||||
|
this.status = await this.request('zwave.status')
|
||||||
|
} finally {
|
||||||
|
this.loading.status = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.views = {
|
||||||
|
nodes: true,
|
||||||
|
scenes: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshNodes()
|
||||||
|
this.refreshGroups()
|
||||||
|
this.refreshScenes()
|
||||||
|
this.refreshValues('switches')
|
||||||
|
this.refreshValues('dimmers')
|
||||||
|
this.refreshValues('sensors')
|
||||||
|
this.refreshValues('bulbs')
|
||||||
|
this.refreshValues('doorlocks')
|
||||||
|
this.refreshValues('usercodes')
|
||||||
|
this.refreshValues('thermostats')
|
||||||
|
this.refreshValues('protections')
|
||||||
|
this.refreshValues('battery_levels')
|
||||||
|
this.refreshValues('power_levels')
|
||||||
|
this.refreshValues('node_config')
|
||||||
|
this.refreshStatus()
|
||||||
|
},
|
||||||
|
|
||||||
|
async addScene() {
|
||||||
|
let name = prompt('Scene name')
|
||||||
|
if (name?.length)
|
||||||
|
name = name.trim()
|
||||||
|
if (!name?.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.create_scene', {label: name})
|
||||||
|
await this.refreshScenes()
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeScene(sceneId) {
|
||||||
|
if (!confirm('Are you sure that you want to delete this scene?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.remove_scene', {scene_id: sceneId})
|
||||||
|
await this.refreshScenes()
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodeUpdate(event) {
|
||||||
|
this.nodes[event.node.node_id] = event.node
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodeClick(nodeId) {
|
||||||
|
this.selected.nodeId = nodeId === this.selected.nodeId ? undefined : nodeId
|
||||||
|
},
|
||||||
|
|
||||||
|
networkInfoModalOpen() {
|
||||||
|
this.refreshStatus()
|
||||||
|
this.$refs.networkInfoModal.show()
|
||||||
|
},
|
||||||
|
|
||||||
|
onCommandEvent(event) {
|
||||||
|
if (event.error && event.error.length) {
|
||||||
|
this.notify({
|
||||||
|
text: event.state_description + ': ' + event.error_description,
|
||||||
|
error: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addNode() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.add_node')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshNodes()
|
||||||
|
},
|
||||||
|
|
||||||
|
async addToGroup(nodeId, groupId) {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.add_node_to_group', {
|
||||||
|
node_id: nodeId,
|
||||||
|
group_index: groupId,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshGroups()
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeNode() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.remove_node')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshNodes()
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeValueFromScene(event) {
|
||||||
|
if (!confirm('Are you sure that you want to remove this value from the scene?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.scene_remove_value', {
|
||||||
|
id_on_network: event.valueId,
|
||||||
|
scene_id: event.sceneId,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshScenes()
|
||||||
|
},
|
||||||
|
|
||||||
|
async renameScene(sceneId) {
|
||||||
|
const scene = this.scenes[sceneId]
|
||||||
|
let name = prompt('New name', scene.label)
|
||||||
|
if (name)
|
||||||
|
name = name.trim()
|
||||||
|
if (!name?.length || name === scene.label)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.set_scene_label', {
|
||||||
|
new_label: name,
|
||||||
|
scene_id: sceneId,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshScenes()
|
||||||
|
},
|
||||||
|
|
||||||
|
async startNetwork() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.start_network')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async stopNetwork() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.stop_network')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async switchAll(state) {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.switch_all', {state: state})
|
||||||
|
this.refresh()
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async cancelCommand() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.cancel_command')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async killCommand() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.kill_command')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async setControllerName() {
|
||||||
|
let name = prompt('Controller name')
|
||||||
|
if (name?.length)
|
||||||
|
name = name.trim()
|
||||||
|
if (!name?.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.set_controller_name', {name: name})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async receiveConfiguration() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.receive_configuration')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async createNewPrimary() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.create_new_primary')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async transferPrimaryRole() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.transfer_primary_role')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async healNetwork() {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.heal')
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async softReset() {
|
||||||
|
if (!confirm("Are you sure that you want to do a device soft reset? This won't lose network information"))
|
||||||
|
return
|
||||||
|
|
||||||
|
await this.request('zwave.soft_reset')
|
||||||
|
},
|
||||||
|
|
||||||
|
async hardReset() {
|
||||||
|
if (!confirm("Are you sure that you want to do a device soft reset? All network information will be LOST!"))
|
||||||
|
return
|
||||||
|
|
||||||
|
await this.request('zwave.hard_reset')
|
||||||
|
},
|
||||||
|
|
||||||
|
async activateScene(sceneId) {
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.activate_scene', {scene_id: sceneId})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addValueToScene(event) {
|
||||||
|
if (!this.selected.valueId)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.scene_add_value', {
|
||||||
|
id_on_network: event.valueId,
|
||||||
|
scene_id: event.sceneId,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.refresh()
|
||||||
|
|
||||||
|
this.subscribe(this.refreshGroups, 'on-zwave-node-group-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeGroupEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.refreshScenes, 'on-zwave-node-scene-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeSceneEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.refreshNodes, 'on-zwave-node-removed-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeRemovedEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.onCommandEvent, 'on-zwave-command-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveCommandEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.refreshStatus, 'on-zwave-network-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveNetworkReadyEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNetworkStoppedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNetworkErrorEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNetworkResetEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.onNodeUpdate, 'on-zwave-node-update-event',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeAddedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeRenamedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveNodeReadyEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveValueAddedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveValueChangedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveValueRemovedEvent',
|
||||||
|
'platypush.message.event.zwave.ZwaveValueRefreshedEvent')
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
[
|
||||||
|
'on-zwave-node-group-event', 'on-zwave-node-scene-event', 'on-zwave-node-removed-event', 'on-zwave-command-event',
|
||||||
|
'on-zwave-network-event', 'on-zwave-node-update-event'
|
||||||
|
].forEach((eventType) => this.unsubscribe(eventType))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "common";
|
||||||
|
|
||||||
|
.zwave-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 .5em;
|
||||||
|
background: $container-bg;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.view-options {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1em 0;
|
||||||
|
|
||||||
|
.view-selector {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,373 @@
|
||||||
|
<template>
|
||||||
|
<div class="item node" :class="{selected: selected}">
|
||||||
|
<div class="row name vertical-center" :class="{selected: selected}"
|
||||||
|
v-text="node.name && node.name.length ? node.name : `<Node ${node.node_id}>`" @click="$emit('select')" />
|
||||||
|
|
||||||
|
<div class="params" v-if="selected">
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Name</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<div :class="{hidden: !editMode.name}">
|
||||||
|
<form ref="nameForm" @submit.prevent="editName">
|
||||||
|
<label>
|
||||||
|
<input type="text" name="name" :value="node.name" :disabled="commandRunning">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="buttons">
|
||||||
|
<button type="button" class="btn btn-default" @click="editMode.name = false">
|
||||||
|
<i class="fas fa-times" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-default" :disabled="commandRunning">
|
||||||
|
<i class="fa fa-check" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="{hidden: editMode.name}">
|
||||||
|
<span v-text="node.name?.length ? node.name : `<Node ${node.node_id}>`" />
|
||||||
|
<span class="buttons">
|
||||||
|
<button type="button" class="btn btn-default" @click="onEditMode('name')" :disabled="commandRunning">
|
||||||
|
<i class="fa fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.location && node.location.length">
|
||||||
|
<div class="param-name">Location</div>
|
||||||
|
<div class="param-value" v-text="node.location" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Type</div>
|
||||||
|
<div class="param-value" v-text="node.type" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Role</div>
|
||||||
|
<div class="param-value" v-text="node.role" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Node ID</div>
|
||||||
|
<div class="param-value" v-text="node.node_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.neighbours.length">
|
||||||
|
<div class="param-name">Neighbours</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<div class="row pull-right" v-for="(neighbour, i) in node.neighbours" :key="i" v-text="neighbour" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Is Ready</div>
|
||||||
|
<div class="param-value" v-text="node.is_ready" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Is Failed</div>
|
||||||
|
<div class="param-value" v-text="node.is_failed" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Product ID</div>
|
||||||
|
<div class="param-value" v-text="node.manufacturer_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Product Type</div>
|
||||||
|
<div class="param-value" v-text="node.product_type" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.product_name?.length">
|
||||||
|
<div class="param-name">Product Name</div>
|
||||||
|
<div class="param-value" v-text="node.product_name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Manufacturer ID</div>
|
||||||
|
<div class="param-value" v-text="node.manufacturer_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.manufacturer_name?.length">
|
||||||
|
<div class="param-name">Manufacturer Name</div>
|
||||||
|
<div class="param-value" v-text="node.manufacturer_name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Capabilities</div>
|
||||||
|
<div class="param-value" v-text="node.capabilities.join(', ')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Command Classes</div>
|
||||||
|
<div class="param-value" v-text="node.command_classes.join(', ')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Groups</div>
|
||||||
|
<div class="param-value" v-text="Object.values(node.groups).map((g) => g.label || '').join(', ')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Home ID</div>
|
||||||
|
<div class="param-value" v-text="node.home_id.toString(16)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Is Awake</div>
|
||||||
|
<div class="param-value" v-text="node.is_awake" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Is Locked</div>
|
||||||
|
<div class="param-value" v-text="node.is_locked" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.last_update">
|
||||||
|
<div class="param-name">Last Update</div>
|
||||||
|
<div class="param-value" v-text="node.last_update" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="node.last_update">
|
||||||
|
<div class="param-name">Max Baud Rate</div>
|
||||||
|
<div class="param-value" v-text="node.max_baud_rate" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section actions">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row error" v-if="node.is_failed" @click="removeFailedNode">
|
||||||
|
<div class="param-name">Remove Failed Node</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-trash" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row error" v-if="node.is_failed" @click="replaceFailedNode">
|
||||||
|
<div class="param-name">Replace Failed Node</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-sync-alt" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="heal">
|
||||||
|
<div class="param-name">Heal Node</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fas fa-wrench" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="replicationSend">
|
||||||
|
<div class="param-name">Replicate info to secondary controller</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-clone" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="requestNetworkUpdate">
|
||||||
|
<div class="param-name">Request network update</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fas fa-wifi" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="requestNeighbourUpdate">
|
||||||
|
<div class="param-name">Request neighbours update</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fas fa-network-wired" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Node",
|
||||||
|
emits: ['select'],
|
||||||
|
mixins: [Utils],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
commandRunning: false,
|
||||||
|
editMode: {
|
||||||
|
name: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async removeFailedNode() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
this.notify({
|
||||||
|
text: 'A command is already running'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Are you sure that you want to remove this node?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.remove_node', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async replaceFailedNode() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
this.notify({
|
||||||
|
text: 'A command is already running'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Are you sure that you want to replace this node?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.replace_node', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async replicationSend() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
this.notify({
|
||||||
|
text: 'A command is already running'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.replication_send', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestNetworkUpdate() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
this.notify({
|
||||||
|
text: 'A command is already running'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.request_network_update', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestNeighbourUpdate() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
this.notify({
|
||||||
|
text: 'A command is already running'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.request_node_neighbour_update', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEditMode(mode) {
|
||||||
|
this.editMode[mode] = true
|
||||||
|
const form = this.$refs[mode + 'Form']
|
||||||
|
const input = form.querySelector('input[type=text]')
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
input.focus()
|
||||||
|
input.select()
|
||||||
|
}, 10)
|
||||||
|
},
|
||||||
|
|
||||||
|
async editName(event) {
|
||||||
|
const name = event.target.querySelector('input[name=name]').value
|
||||||
|
this.commandRunning = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.request('zwave.set_node_name', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
new_name: name,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editMode.name = false
|
||||||
|
},
|
||||||
|
|
||||||
|
async heal() {
|
||||||
|
if (this.commandRunning) {
|
||||||
|
console.log('A command is already running')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.node_heal', {
|
||||||
|
node_id: this.node.node_id,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "common";
|
||||||
|
</style>
|
|
@ -0,0 +1,224 @@
|
||||||
|
<template>
|
||||||
|
<div class="section value">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">
|
||||||
|
<button class="btn btn-default btn-value-name-edit" title="Edit value name" :disabled="commandRunning"
|
||||||
|
:data-id-on-network="value.id_on_network" @click="editName">
|
||||||
|
<i class="fa fa-edit" />
|
||||||
|
</button>
|
||||||
|
{{ value.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Value</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<div class="value-view" v-if="value.is_read_only">
|
||||||
|
<div class="value-data" v-text="value.data" ></div>
|
||||||
|
<div class="unit" v-text="value.units" v-if="value.units?.length" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="value-edit" v-else>
|
||||||
|
<div :class="['col-' + (value.units?.length ? '11' : '12')]">
|
||||||
|
<div class="list" v-if="value.type === 'List'">
|
||||||
|
<label>
|
||||||
|
<select @change="onValueChange"
|
||||||
|
:data-id-on-network="value.id_on_network">
|
||||||
|
<option v-for="(data, index) in value.data_items"
|
||||||
|
v-text="data"
|
||||||
|
:key="index"
|
||||||
|
:selected="value.data === data"
|
||||||
|
:value="index">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="numeric slider-container" v-else-if="['Byte', 'Decimal', 'Short'].indexOf(value.type) >= 0">
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<span class="value-min" v-text="value.min" />
|
||||||
|
<span class="value-max" v-text="value.max" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
<input class="slider" type="range" :min="value.min" :max="value.max"
|
||||||
|
:value="value.data" :data-id-on-network="value.id_on_network"
|
||||||
|
@change="onValueChange">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<label>
|
||||||
|
<input type="text" :data-id-on-network="value.id_on_network" :value="value.data"
|
||||||
|
@change="onValueChange">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="boolean" v-else-if="['Bool', 'Button'].indexOf(value.type) >= 0">
|
||||||
|
<ToggleSwitch :value="value.data" :data-id-on-network="value.id_on_network" @input="onValueChange" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="value-data" v-text="value.data" v-else />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-1 unit" v-text="value.units" v-if="value.units?.length" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="sceneId != null" style="cursor: pointer"
|
||||||
|
@click="$emit('remove-from-scene', {valueId: value.id_on_network, sceneId: sceneId})">
|
||||||
|
<div class="param-name">Remove From Scene</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="cursor: pointer" v-if="addValueToSceneItems?.length">
|
||||||
|
<div class="param-name">Add To Scene</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<Dropdown title="Add to scene" icon-class="fa fa-plus">
|
||||||
|
<DropdownItem v-for="(scene, i) in addValueToSceneItems" :key="i"
|
||||||
|
:text="scene.label" :disabled="commandRunning"
|
||||||
|
@click="$emit('add-to-scene', {sceneId: scene.scene_id, valueId: value.id_on_network})" />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="value.help?.length">
|
||||||
|
<div class="param-name">Help</div>
|
||||||
|
<div class="param-value" v-text="value.help"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Value ID</div>
|
||||||
|
<div class="param-value" v-text="value.value_id"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">ID on Network</div>
|
||||||
|
<div class="param-value" v-text="value.id_on_network"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Command Class</div>
|
||||||
|
<div class="param-value" v-text="value.command_class"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="value.last_update">
|
||||||
|
<div class="param-name">Last Update</div>
|
||||||
|
<div class="param-value" v-text="value.last_update"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Value",
|
||||||
|
components: {Dropdown, DropdownItem, ToggleSwitch},
|
||||||
|
mixins: [Utils],
|
||||||
|
emits: ['remove-from-scene', 'add-to-scene', 'refresh'],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
sceneId: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
scenes: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { return {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
commandRunning: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
addValueToSceneItems() {
|
||||||
|
return Object.values(this.scenes || {}).filter((scene) => {
|
||||||
|
return !(this.value.id_on_network in scene.values)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async editName(event) {
|
||||||
|
const value = this.node.values[event.target.parentElement.dataset.idOnNetwork]
|
||||||
|
let name = prompt('New name', value.label)
|
||||||
|
if (name?.length)
|
||||||
|
name = name.trim()
|
||||||
|
if (!name?.length || name === value.label)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
await this.request('zwave.set_value_label', {
|
||||||
|
id_on_network: value.id_on_network,
|
||||||
|
new_label: name,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('refresh')
|
||||||
|
this.notify({
|
||||||
|
text: 'Value successfully renamed',
|
||||||
|
image: {
|
||||||
|
iconClass: 'fa fa-check'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async onValueChange(event) {
|
||||||
|
const target = event.target ? event.target : event.event.target.parentElement
|
||||||
|
const value = this.node.values[target.dataset.idOnNetwork]
|
||||||
|
const data = value.type === 'List' ? value.data_items[event.target.value] : (target.value || event.value)
|
||||||
|
|
||||||
|
this.commandRunning = true
|
||||||
|
try {
|
||||||
|
this.request('zwave.set_value', {
|
||||||
|
id_on_network: value.id_on_network,
|
||||||
|
data: data,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.commandRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('refresh')
|
||||||
|
this.notify({
|
||||||
|
text: 'Value successfully modified',
|
||||||
|
image: {
|
||||||
|
iconClass: 'fa fa-check'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "common";
|
||||||
|
</style>
|
|
@ -0,0 +1,225 @@
|
||||||
|
@import "vars";
|
||||||
|
|
||||||
|
.zwave-container {
|
||||||
|
.no-items {
|
||||||
|
padding: 2em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: $no-items-color;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node, .scene {
|
||||||
|
.actions {
|
||||||
|
.row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.params {
|
||||||
|
background: $params-bg;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 1em;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: $param-section-header-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 1em;
|
||||||
|
padding: .3em;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: $param-even-row-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: $param-odd-row-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-name {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 40%;
|
||||||
|
margin-left: 1%;
|
||||||
|
vertical-align: top;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-value {
|
||||||
|
display: inline-block;
|
||||||
|
width: 58%;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.value-edit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-data {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: .8em;
|
||||||
|
margin-left: 1em;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numeric {
|
||||||
|
input.slider {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
background: none;
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-min, .value-max {
|
||||||
|
width: 50%;
|
||||||
|
font-size: .85em;
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-min {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-max {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default {
|
||||||
|
border: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: $default-border-2;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view {
|
||||||
|
min-width: 400pt;
|
||||||
|
max-width: 750pt;
|
||||||
|
background: $view-bg;
|
||||||
|
border: $view-border;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-shadow: $view-box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
&.selected {
|
||||||
|
box-shadow: $selected-item-box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: $item-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 1.5em 1.5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0 1.5em 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-value-name-edit {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
.section {
|
||||||
|
.header {
|
||||||
|
background: none;
|
||||||
|
padding: .5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-info {
|
||||||
|
min-width: 600pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: $error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
$container-bg: #f1f1f1;
|
||||||
|
$view-bg: white;
|
||||||
|
$view-border: 1px solid #d8d8d8;
|
||||||
|
$view-box-shadow: 1px 2px 2px #ccc;
|
||||||
|
$item-border: 1px solid #dddddd;
|
||||||
|
$no-items-color: #555555;
|
||||||
|
$params-bg: white;
|
||||||
|
$param-even-row-bg: #ededed;
|
||||||
|
$param-odd-row-bg: white;
|
||||||
|
$param-section-header-border: 1px solid #e8e8e8;
|
||||||
|
$selected-item-box-shadow: 0 2px 4px 0 #bbb;
|
||||||
|
$error-color: #aa0000;
|
Loading…
Reference in a new issue