diff --git a/front/src/actions/integration.js b/front/src/actions/integration.js index e7884af21b..a3c2ba4572 100644 --- a/front/src/actions/integration.js +++ b/front/src/actions/integration.js @@ -20,13 +20,17 @@ const actions = store => ({ }); }, async getIntegrationByName(state, name, podId = null) { - const query = { - pod_id: podId - }; - const currentIntegration = await state.httpClient.get(`/api/v1/service/${name}`, query); - store.setState({ - currentIntegration - }); + try { + const query = { + pod_id: podId + }; + const currentIntegration = await state.httpClient.get(`/api/v1/service/${name}`, query); + store.setState({ + currentIntegration + }); + } catch (e) { + console.log(e); + } }, getIntegrationByCategory(state, category) { const userLanguage = getLanguage(state); diff --git a/front/src/components/app.jsx b/front/src/components/app.jsx index 9e363fdd46..5857e96ab8 100644 --- a/front/src/components/app.jsx +++ b/front/src/components/app.jsx @@ -63,6 +63,7 @@ import ZwaveNetworkPage from '../routes/integration/all/zwave/network-page'; import ZwaveSettingsPage from '../routes/integration/all/zwave/settings-page'; import ZwaveSetupPage from '../routes/integration/all/zwave/setup-page'; import ZwaveNodeOperationPage from '../routes/integration/all/zwave/node-operation-page'; +import ZwaveEditPage from '../routes/integration/all/zwave/edit-page'; import RtspCameraPage from '../routes/integration/all/rtsp-camera'; import XiaomiPage from '../routes/integration/all/xiaomi'; import EditXiaomiPage from '../routes/integration/all/xiaomi/edit-page'; @@ -160,6 +161,7 @@ const AppRouter = connect( + diff --git a/front/src/components/boxs/device-in-room/DeviceRow.jsx b/front/src/components/boxs/device-in-room/DeviceRow.jsx index 1e2ff1810b..dbaf3369f0 100644 --- a/front/src/components/boxs/device-in-room/DeviceRow.jsx +++ b/front/src/components/boxs/device-in-room/DeviceRow.jsx @@ -9,7 +9,7 @@ const DeviceRow = ({ children, ...props }) => { // if device is a sensor, we display the sensor deviceFeature if (props.deviceFeature.read_only) { - return ; + return ; } // else, it's not a sensor diff --git a/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx b/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx index 9a0ba37059..65d6ea5379 100644 --- a/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx +++ b/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx @@ -56,6 +56,7 @@ const RoomCard = ({ children, ...props }) => { props.devices.map((device, deviceIndex) => device.features.map((deviceFeature, deviceFeatureIndex) => ( { ); }; -@connect('session,DashboardBoxDataDevicesInRoom,DashboardBoxStatusDevicesInRoom', actions) +@connect('session,user,DashboardBoxDataDevicesInRoom,DashboardBoxStatusDevicesInRoom', actions) class DevicesInRoomComponent extends Component { updateDeviceStateWebsocket = payload => this.props.deviceFeatureWebsocketEvent(this.props.x, this.props.y, payload); diff --git a/front/src/components/boxs/device-in-room/device-features/SensorDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/SensorDeviceFeature.jsx index 142deaf958..b35e482f89 100644 --- a/front/src/components/boxs/device-in-room/device-features/SensorDeviceFeature.jsx +++ b/front/src/components/boxs/device-in-room/device-features/SensorDeviceFeature.jsx @@ -1,35 +1,37 @@ import { Text } from 'preact-i18n'; -import { DEVICE_FEATURE_UNITS } from '../../../../../../server/utils/constants'; -const OPEN_CLOSE_SENSORS = ['door-opening-sensor', 'window-opening-sensor']; +import get from 'get-value'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +dayjs.extend(relativeTime); + +import { DEVICE_FEATURE_UNITS, DEVICE_FEATURE_CATEGORIES } from '../../../../../../server/utils/constants'; + +const SPECIAL_SENSORS = [DEVICE_FEATURE_CATEGORIES.OPENING_SENSOR, DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR]; + +import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts'; const SensorDeviceType = ({ children, ...props }) => ( - {props.deviceFeature.category === 'temperature-sensor' && } - {props.deviceFeature.category === 'humidity-sensor' && } - {props.deviceFeature.category === 'light-sensor' && } - {props.deviceFeature.category === 'battery-sensor' && } - {props.deviceFeature.category === 'switch' && props.deviceFeature.type === 'power' && } - {OPEN_CLOSE_SENSORS.indexOf(props.deviceFeature.category) !== -1 && } - {props.deviceFeature.category === null && } + - {props.deviceFeature.name && {props.deviceFeature.name}} - {!props.deviceFeature.name && props.deviceFeature.type === 'binary' && {props.deviceFeature.name}} - {!props.deviceFeature.name && props.deviceFeature.type !== 'binary' && ( - - {props.deviceFeature.name} - {props.deviceFeature.type} - - )} - {OPEN_CLOSE_SENSORS.indexOf(props.deviceFeature.category) === -1 && ( + {props.deviceFeature.name} + {SPECIAL_SENSORS.indexOf(props.deviceFeature.category) === -1 && ( {props.deviceFeature.last_value !== null && props.deviceFeature.last_value} {props.deviceFeature.last_value === null && } - {props.deviceFeature.category === 'temperature-sensor' && props.deviceFeature.last_value !== null && ( - {props.deviceFeature.unit === 'celsius' ? '°C' : '°F'} - )} - {props.deviceFeature.category !== 'temperature-sensor' && props.deviceFeature.last_value !== null && ( + {props.deviceFeature.last_value !== null && ( + {' '} {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.PERCENT && '%'} + {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.CELSIUS && '°C'} + {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.FAHRENHEIT && '°F'} {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.KILOWATT && 'kW'} {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.KILOWATT_HOUR && 'kW/h'} {props.deviceFeature.unit === DEVICE_FEATURE_UNITS.LUX && 'Lx'} @@ -38,12 +40,19 @@ const SensorDeviceType = ({ children, ...props }) => ( )} )} - {OPEN_CLOSE_SENSORS.indexOf(props.deviceFeature.category) !== -1 && ( + {props.deviceFeature.category === DEVICE_FEATURE_CATEGORIES.OPENING_SENSOR && ( {props.deviceFeature.last_value === 1 && } {props.deviceFeature.last_value === 0 && } )} + {props.deviceFeature.category === DEVICE_FEATURE_CATEGORIES.MOTION_SENSOR && ( + + {dayjs(props.deviceFeature.last_value_changed) + .locale(props.user.language) + .fromNow()} + + )} ); diff --git a/front/src/config/demo.json b/front/src/config/demo.json index 33755bcd18..d434eba1e0 100644 --- a/front/src/config/demo.json +++ b/front/src/config/demo.json @@ -244,7 +244,7 @@ "name": "Temperature", "selector": "temperature-living-room-celsius", "category": "temperature-sensor", - "type": "", + "type": "decimal", "unit": "celsius", "min": -200, "max": 200, @@ -269,7 +269,7 @@ "name": "Temperature", "selector": "temperature-living-room-celsius", "category": "temperature-sensor", - "type": "", + "type": "decimal", "unit": "celsius", "min": -200, "max": 200, @@ -281,8 +281,8 @@ "name": "Humidity", "selector": "temperature-living-room-celsius", "category": "humidity-sensor", - "type": "", - "unit": "%", + "type": "decimal", + "unit": "percent", "min": -200, "max": 200, "read_only": true, @@ -292,9 +292,9 @@ { "name": "Kitchen door", "selector": "temperature-living-room-celsius", - "category": "door-opening-sensor", - "type": "", - "unit": "%", + "category": "opening-sensor", + "type": "binary", + "unit": null, "min": -200, "max": 200, "read_only": true, @@ -513,38 +513,18 @@ }, "get /api/v1/service/zwave/node": [ { - "id": "1", - "manufacturer": "Z-Wave.Me", - "manufacturerid": "0x0115", - "product": "ZME_UZB1 USB Stick", - "producttype": "0x0400", - "productid": "0x0001", - "type": "Static PC Controller", - "name": "", - "loc": "", - "classes": { - "32": { - "0": { - "value_id": "1-32-1-0", - "node_id": 1, - "class_id": 32, - "type": "byte", - "genre": "basic", - "instance": 1, - "index": 0, - "label": "Basic", - "units": "", - "help": "", - "read_only": false, - "write_only": false, - "min": 0, - "max": 255, - "is_polled": false, - "value": 0 - } - } - }, - "ready": true + "name": "ZME_UZB1 USB Stick", + "features": [], + "params": [], + "rawZwaveNode": { + "id": 1, + "manufacturer": "Z-Wave.Me", + "manufacturerid": "0x0115", + "product": "ZME_UZB1 USB Stick", + "producttype": "0x0400", + "productid": "0x0001", + "type": "Static PC Controller" + } } ], "get /api/v1/service/zwave/neighbor": [ @@ -644,7 +624,7 @@ "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", "room_id": "cecc52c7-3e67-4b75-9b13-9a8867b0443d", "name": "Fibaro Motion Sensor", - "selector": "test-sensor", + "selector": "zwave:1234", "external_id": "test-sensor-external", "should_poll": false, "poll_frequency": null, @@ -975,6 +955,60 @@ "name": "Xiaomi", "selector": "xiaomi" }, + "get /api/v1/device/zwave:1234": { + "id": "fbedb47f-4d25-4381-8923-2633b23192a0", + "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", + "room_id": "cecc52c7-3e67-4b75-9b13-9a8867b0443d", + "name": "Fibaro Motion Sensor", + "selector": "zwave:1234", + "external_id": "test-sensor-external", + "should_poll": false, + "poll_frequency": null, + "created_at": "2019-02-12T07:49:07.556Z", + "updated_at": "2019-02-12T07:49:07.556Z", + "features": [ + { + "name": "Temperature", + "external_id": "zwave:1234:temperature", + "selector": "test-temperature", + "category": "temperature-sensor", + "unit": "celsius", + "type": "decimal" + }, + { + "name": "Motion", + "selector": "test-motion", + "external_id": "zwave:1234:temperature", + "category": "motion-sensor", + "type": "binary" + }, + { + "name": "Battery", + "selector": "test-battery", + "external_id": "zwave:1234:temperature", + "category": "battery", + "type": "integer", + "last_value": "92" + }, + { + "name": "Lux", + "selector": "test-light", + "external_id": "zwave:1234:temperature", + "category": "light-sensor", + "type": "integer" + } + ], + "room": { + "id": "cecc52c7-3e67-4b75-9b13-9a8867b0443d", + "name": "Living Room", + "selector": "living-room" + } + }, + "get /api/v1/service/zwave": { + "id": "a810b8db-6d04-4697-bed3-c4b72c996279", + "name": "Zwave", + "selector": "zwave" + }, "get /api/v1/device/xiaomi:1234": { "id": "e5317b24-28e1-4839-9879-0bb7a3102e98", "name": "Xiaomi Temperature", diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 2b348f1386..67c0ac5502 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -279,7 +279,8 @@ "featuresLabel": "Features", "noFeatures": "No features", "saveButton": "Save", - "deleteButton": "Delete" + "deleteButton": "Delete", + "editButton": "Edit" }, "setup": { "title": "Z-Wave Devices", @@ -296,6 +297,7 @@ "features": "Features", "params": "Params", "noFeatures": "No features", + "nodeId": "Node", "zwaveNotConfiguredError": "Z-wave is not configured. Please select the USB port where you Z-Wave key is plugged in settings.", "createDeviceError": "There was an error while creating this device in Gladys.", "deviceCreatedSuccess": "The device was added with success." @@ -321,7 +323,9 @@ "addNodeTitle": "Inclusion Mode", "removeNodeTitle": "Exclusion Mode", "seconds": "seconds remaining", - "cancelButton": "Cancel" + "cancelButton": "Cancel", + "nodeAddedTitle": "A new node was found", + "nodeAddedDescription": "Wait a few seconds while we get all informations from this node..." } }, "darkSky": { diff --git a/front/src/routes/integration/all/zwave/edit-page/index.js b/front/src/routes/integration/all/zwave/edit-page/index.js new file mode 100644 index 0000000000..6f4fc63f8a --- /dev/null +++ b/front/src/routes/integration/all/zwave/edit-page/index.js @@ -0,0 +1,20 @@ +import { Component } from 'preact'; +import { connect } from 'unistore/preact'; +// import actions from '../actions'; +import ZwavePage from '../ZwavePage'; +import UpdateDevice from '../../../../../components/device'; + +const ZWAVE_PAGE_PATH = '/dashboard/integration/device/zwave'; + +@connect('user,session,httpClient,currentIntegration,houses', {}) +class EditZwaveDevice extends Component { + render(props, {}) { + return ( + + + + ); + } +} + +export default EditZwaveDevice; diff --git a/front/src/routes/integration/all/zwave/node-operation-page/AddRemoveNode.jsx b/front/src/routes/integration/all/zwave/node-operation-page/AddRemoveNode.jsx index 80710f4f0a..b7d9c5170d 100644 --- a/front/src/routes/integration/all/zwave/node-operation-page/AddRemoveNode.jsx +++ b/front/src/routes/integration/all/zwave/node-operation-page/AddRemoveNode.jsx @@ -17,18 +17,30 @@ const AddNode = ({ children, ...props }) => (
-
-

- {props.remainingTimeInSeconds} -

-

- {props.action === 'remove' ? ( - - ) : ( - - )} -

-
+ {!props.nodeAdded && ( +
+

+ {props.remainingTimeInSeconds} +

+

+ {props.action === 'remove' ? ( + + ) : ( + + )} +

+
+ )} + {props.nodeAdded && ( +
+

+ +

+

+ +

+
+ )}
); diff --git a/front/src/routes/integration/all/zwave/node-operation-page/index.js b/front/src/routes/integration/all/zwave/node-operation-page/index.js index 988d941fa2..3dbf862c5a 100644 --- a/front/src/routes/integration/all/zwave/node-operation-page/index.js +++ b/front/src/routes/integration/all/zwave/node-operation-page/index.js @@ -4,9 +4,25 @@ import { route } from 'preact-router'; import actions from './actions'; import ZwavePage from '../ZwavePage'; import NodeOperationPage from './AddRemoveNode'; +import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../../../server/utils/constants'; @connect('session,user,zwaveDevices,houses,getZwaveDevicesStatus', actions) class ZwaveNodeOperationPage extends Component { + nodeAddedListener = () => { + this.setState({ + nodeAdded: true + }); + }; + nodeReadyListener = () => { + if (this.props.action === 'add' || this.props.action === 'add-secure') { + route('/dashboard/integration/device/zwave/setup'); + } + }; + nodeRemovedListener = () => { + if (this.props.action === 'remove') { + route('/dashboard/integration/device/zwave/setup'); + } + }; decrementTimer = () => { this.setState(prevState => { return { remainingTimeInSeconds: prevState.remainingTimeInSeconds - 1 }; @@ -51,12 +67,26 @@ class ZwaveNodeOperationPage extends Component { this.removeNode(); break; } + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_ADDED, this.nodeAddedListener); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_READY, this.nodeReadyListener); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_REMOVED, this.nodeRemovedListener); + } + + componentWillUnmount() { + this.props.session.dispatcher.removeListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_ADDED, this.nodeAddedListener); + this.props.session.dispatcher.removeListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_READY, this.nodeReadyListener); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_REMOVED, this.nodeRemovedListener); } - render(props, { remainingTimeInSeconds }) { + render(props, { remainingTimeInSeconds, nodeAdded }) { return ( - + ); } diff --git a/front/src/routes/integration/all/zwave/node-page/Device.jsx b/front/src/routes/integration/all/zwave/node-page/Device.jsx index ed302b7d59..ebd2e00623 100644 --- a/front/src/routes/integration/all/zwave/node-page/Device.jsx +++ b/front/src/routes/integration/all/zwave/node-page/Device.jsx @@ -2,6 +2,7 @@ import { Text, Localizer } from 'preact-i18n'; import { Component } from 'preact'; import cx from 'classnames'; import get from 'get-value'; +import { Link } from 'preact-router/match'; import { DEVICE_FEATURE_CATEGORIES } from '../../../../../../../server/utils/constants'; import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; @@ -136,6 +137,11 @@ class ZWaveDeviceBox extends Component { + + + diff --git a/front/src/routes/integration/all/zwave/setup-page/Node.jsx b/front/src/routes/integration/all/zwave/setup-page/Node.jsx index 762e4fa991..c214151ff8 100644 --- a/front/src/routes/integration/all/zwave/setup-page/Node.jsx +++ b/front/src/routes/integration/all/zwave/setup-page/Node.jsx @@ -19,10 +19,15 @@ class ZwaveNode extends Component { render(props, { loading, error, deviceCreated }) { return ( -
+
-

{props.node.product}

+

{props.node.name}

+
+ + {props.node.rawZwaveNode.id} + +
)}
-
- - -
- +
{props.node.features.length > 0 && (
diff --git a/front/src/routes/integration/all/zwave/setup-page/NodeTab.jsx b/front/src/routes/integration/all/zwave/setup-page/NodeTab.jsx index 09a242b620..362293669c 100644 --- a/front/src/routes/integration/all/zwave/setup-page/NodeTab.jsx +++ b/front/src/routes/integration/all/zwave/setup-page/NodeTab.jsx @@ -8,6 +8,11 @@ import { RequestStatus } from '../../../../../utils/consts'; const NodeTab = ({ children, ...props }) => { const zwaveNotConfigured = props.zwaveGetNodesStatus === RequestStatus.ServiceNotConfigured; + const scanInProgress = get(props, 'zwaveStatus.scanInProgress'); + const healInProgress = props.zwaveHealNetworkStatus === RequestStatus.Getting; + const gettingNodesInProgress = props.zwaveGetNodesStatus === RequestStatus.Getting; + const zwaveActionsDisabled = scanInProgress || healInProgress || gettingNodesInProgress; + const zwaveActionsEnabled = !zwaveActionsDisabled; return (
@@ -15,36 +20,30 @@ const NodeTab = ({ children, ...props }) => {
@@ -56,7 +55,7 @@ const NodeTab = ({ children, ...props }) => { )}
{props.zwaveNodes && diff --git a/front/src/routes/integration/all/zwave/setup-page/actions.js b/front/src/routes/integration/all/zwave/setup-page/actions.js index 18b86533a5..971c5ebbdd 100644 --- a/front/src/routes/integration/all/zwave/setup-page/actions.js +++ b/front/src/routes/integration/all/zwave/setup-page/actions.js @@ -1,79 +1,19 @@ import get from 'get-value'; -import update from 'immutability-helper'; import { RequestStatus } from '../../../../../utils/consts'; -import { getCategory } from '../../../../../../../server/services/zwave/lib/utils/getCategory'; -import { getDeviceFeatureExternalId } from '../../../../../../../server/services/zwave/lib/utils/externalId'; import { ERROR_MESSAGES } from '../../../../../../../server/utils/constants'; import createActionsIntegration from '../../../../../actions/integration'; -const addParamsAndFeatures = node => { - node.features = []; - node.params = []; - const comclasses = Object.keys(node.classes); - comclasses.forEach(comclass => { - const values = node.classes[comclass]; - const indexes = Object.keys(values); - indexes.forEach(idx => { - const { min, max } = values[idx]; - - if (values[idx].genre === 'user') { - const { category, type } = getCategory(node, values[idx]); - if (category !== 'unknown') { - node.features.push({ - name: `${values[idx].label} - ${node.product} - Node ${node.id}`, - category, - type, - external_id: getDeviceFeatureExternalId(values[idx]), - read_only: values[idx].read_only, - unit: values[idx].units, - has_feedback: true, - min, - max - }); - } - } else { - node.params.push({ - name: `${values[idx].label}-${values[idx].value_id}`, - value: values[idx].value || '' - }); - } - }); - }); -}; - const createActions = store => { const integrationActions = createActionsIntegration(store); const actions = { - addLocalNode(state, node) { - addParamsAndFeatures(node); - const nodeInArrayIndex = state.zwaveNodes.findIndex(n => n.id === node.id); - let newState; - if (nodeInArrayIndex !== -1) { - newState = update(state, { - zwaveNodes: { - $push: [node] - } - }); - } else { - newState = update(state, { - zwaveNodes: { - [nodeInArrayIndex]: { - $set: node - } - } - }); - } - store.setState(newState); - }, async getNodes(state) { store.setState({ zwaveGetNodesStatus: RequestStatus.Getting }); try { const zwaveNodes = await state.httpClient.get('/api/v1/service/zwave/node'); - zwaveNodes.forEach(node => addParamsAndFeatures(node)); - console.log(zwaveNodes); + store.setState({ zwaveNodes, zwaveGetNodesStatus: RequestStatus.Success @@ -138,6 +78,7 @@ const createActions = store => { store.setState({ zwaveHealNetworkStatus: RequestStatus.Success }); + actions.getStatus(store.getState()); } catch (e) { store.setState({ zwaveHealNetworkStatus: RequestStatus.Error @@ -160,44 +101,7 @@ const createActions = store => { }); } }, - async createDevice(state, node) { - const newDevice = { - name: node.product, - service_id: state.currentIntegration.id, - external_id: `zwave:node_id:${node.id}`, - features: [], - params: [] - }; - const comclasses = Object.keys(node.classes); - comclasses.forEach(comclass => { - const values = node.classes[comclass]; - const indexes = Object.keys(values); - indexes.forEach(idx => { - const { min, max } = values[idx]; - - if (values[idx].genre === 'user') { - const { category, type } = getCategory(node, values[idx]); - if (category !== 'unknown') { - newDevice.features.push({ - name: `${values[idx].label} - ${node.product} - Node ${node.id}`, - category, - type, - external_id: getDeviceFeatureExternalId(values[idx]), - read_only: values[idx].read_only, - unit: values[idx].units, - has_feedback: true, - min, - max - }); - } - } else { - newDevice.params.push({ - name: `${values[idx].label}-${values[idx].value_id}`, - value: values[idx].value || '' - }); - } - }); - }); + async createDevice(state, newDevice) { await state.httpClient.post('/api/v1/device', newDevice); } }; diff --git a/front/src/routes/integration/all/zwave/setup-page/index.js b/front/src/routes/integration/all/zwave/setup-page/index.js index c75eb32d38..2b04b55db3 100644 --- a/front/src/routes/integration/all/zwave/setup-page/index.js +++ b/front/src/routes/integration/all/zwave/setup-page/index.js @@ -6,19 +6,19 @@ import NodeTab from './NodeTab'; import integrationConfig from '../../../../../config/integrations'; import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../../../server/utils/constants'; -@connect('user,session,zwaveNodes,zwaveStatus,zwaveGetNodesStatus', actions) +@connect('user,session,zwaveNodes,zwaveStatus,zwaveGetNodesStatus,zwaveHealNetworkStatus', actions) class ZwaveNodePage extends Component { - nodeReadyListener = payload => this.props.addLocalNode(payload); + nodeReadyListener = payload => this.props.getNodes(); scanCompleteListener = () => { this.props.getStatus(); this.props.getNodes(); }; componentWillMount() { + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_READY, this.nodeReadyListener); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.SCAN_COMPLETE, this.scanCompleteListener); this.props.getIntegrationByName('zwave'); this.props.getNodes(); this.props.getStatus(); - this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_READY, this.nodeReadyListener); - this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.ZWAVE.SCAN_COMPLETE, this.scanCompleteListener); } componentWillUnmount() { diff --git a/server/lib/device/device.newStateEvent.js b/server/lib/device/device.newStateEvent.js index df19cf1d73..49bbfd3f0b 100644 --- a/server/lib/device/device.newStateEvent.js +++ b/server/lib/device/device.newStateEvent.js @@ -17,10 +17,7 @@ async function newStateEvent(event) { } await this.saveState(deviceFeature, event.state); } catch (e) { - logger.error( - `Unable to save new state of deviceFeature ${event.device_feature_external_id}, state = ${event.state}`, - ); - logger.error(e); + logger.debug(e); } } diff --git a/server/services/zwave/lib/commands/zwave.getNodes.js b/server/services/zwave/lib/commands/zwave.getNodes.js index 54f84235cd..f9aacc0af5 100644 --- a/server/services/zwave/lib/commands/zwave.getNodes.js +++ b/server/services/zwave/lib/commands/zwave.getNodes.js @@ -1,4 +1,8 @@ const { ServiceNotConfiguredError } = require('../../../../utils/coreErrors'); +const { slugify } = require('../../../../utils/slugify'); +const { getCategory } = require('../utils/getCategory'); +const { getUnit } = require('../utils/getUnit'); +const { getDeviceFeatureExternalId } = require('../utils/externalId'); /** * @description Return array of Nodes. @@ -11,7 +15,61 @@ function getNodes() { throw new ServiceNotConfiguredError('ZWAVE_DRIVER_NOT_RUNNING'); } const nodeIds = Object.keys(this.nodes); - return nodeIds.map((nodeId) => Object.assign({}, { id: nodeId }, this.nodes[nodeId])); + + // transform object in array + let nodes = nodeIds.map((nodeId) => Object.assign({}, { id: nodeId }, this.nodes[nodeId])); + + // remove non-ready nodes + nodes = nodes.filter((node) => node.ready === true); + + // foreach node in RAM, we format it with the gladys device format + return nodes.map((node) => { + const newDevice = { + name: node.product, + service_id: this.serviceId, + external_id: `zwave:node_id:${node.id}`, + rawZwaveNode: { + id: node.id, + manufacturer: node.manufacturer, + type: node.type, + }, + features: [], + params: [], + }; + + const comclasses = Object.keys(node.classes); + comclasses.forEach((comclass) => { + const values = node.classes[comclass]; + const indexes = Object.keys(values); + indexes.forEach((idx) => { + const { min, max } = values[idx]; + + if (values[idx].genre === 'user') { + const { category, type } = getCategory(node, values[idx]); + if (category !== 'unknown') { + newDevice.features.push({ + name: values[idx].label, + selector: slugify(`zwave-${values[idx].label}-${node.product}-node-${node.id}`), + category, + type, + external_id: getDeviceFeatureExternalId(values[idx]), + read_only: values[idx].read_only, + unit: getUnit(values[idx].units), + has_feedback: true, + min, + max, + }); + } + } else { + newDevice.params.push({ + name: slugify(`${values[idx].label}-${values[idx].value_id}`), + value: values[idx].value || '', + }); + } + }); + }); + return newDevice; + }); } module.exports = { diff --git a/server/services/zwave/lib/events/zwave.nodeAdded.js b/server/services/zwave/lib/events/zwave.nodeAdded.js index ccc1c90113..b0ec0fc624 100644 --- a/server/services/zwave/lib/events/zwave.nodeAdded.js +++ b/server/services/zwave/lib/events/zwave.nodeAdded.js @@ -1,4 +1,5 @@ const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); /** * @description When a node is added. @@ -20,6 +21,10 @@ function nodeAdded(nodeId) { classes: {}, ready: false, }; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_ADDED, + payload: nodeId, + }); } module.exports = { diff --git a/server/services/zwave/lib/events/zwave.nodeRemoved.js b/server/services/zwave/lib/events/zwave.nodeRemoved.js new file mode 100644 index 0000000000..599e282af8 --- /dev/null +++ b/server/services/zwave/lib/events/zwave.nodeRemoved.js @@ -0,0 +1,21 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When a node is removed. + * @param {number} nodeId - The ID of the node. + * @example + * zwave.on('node removed', this.nodeRemoved); + */ +function nodeRemoved(nodeId) { + logger.debug(`Zwave : Node removed, nodeId = ${nodeId}`); + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.ZWAVE.NODE_REMOVED, + payload: nodeId, + }); + delete this.nodes[nodeId]; +} + +module.exports = { + nodeRemoved, +}; diff --git a/server/services/zwave/lib/index.js b/server/services/zwave/lib/index.js index 2ccc021cf2..cb3032569d 100644 --- a/server/services/zwave/lib/index.js +++ b/server/services/zwave/lib/index.js @@ -4,6 +4,7 @@ const fs = require('fs'); const { driverReady } = require('./events/zwave.driverReady'); const { driverFailed } = require('./events/zwave.driverFailed'); const { nodeAdded } = require('./events/zwave.nodeAdded'); +const { nodeRemoved } = require('./events/zwave.nodeRemoved'); const { nodeEvent } = require('./events/zwave.nodeEvent'); const { valueAdded } = require('./events/zwave.valueAdded'); const { valueChanged } = require('./events/zwave.valueChanged'); @@ -49,6 +50,7 @@ const ZwaveManager = function ZwaveManager(Zwave, eventManager, serviceId) { this.zwave.on('driver ready', this.driverReady.bind(this)); this.zwave.on('driver failed', this.driverFailed.bind(this)); this.zwave.on('node added', this.nodeAdded.bind(this)); + this.zwave.on('node removed', this.nodeRemoved.bind(this)); this.zwave.on('node event', this.nodeEvent.bind(this)); this.zwave.on('value added', this.valueAdded.bind(this)); this.zwave.on('value changed', this.valueChanged.bind(this)); @@ -62,6 +64,7 @@ const ZwaveManager = function ZwaveManager(Zwave, eventManager, serviceId) { ZwaveManager.prototype.driverReady = driverReady; ZwaveManager.prototype.driverFailed = driverFailed; ZwaveManager.prototype.nodeAdded = nodeAdded; +ZwaveManager.prototype.nodeRemoved = nodeRemoved; ZwaveManager.prototype.nodeEvent = nodeEvent; ZwaveManager.prototype.valueAdded = valueAdded; ZwaveManager.prototype.valueChanged = valueChanged; diff --git a/server/services/zwave/lib/utils/getUnit.js b/server/services/zwave/lib/utils/getUnit.js new file mode 100644 index 0000000000..9bf5ca27dd --- /dev/null +++ b/server/services/zwave/lib/utils/getUnit.js @@ -0,0 +1,27 @@ +const { DEVICE_FEATURE_UNITS } = require('../../../../utils/constants'); + +/** + * @description Convert Z-Wave unit in Gladys unit. + * @param {string} zwaveUnit - Unit in Z-Wave. + * @returns {string} Return the unit in Gladys. + * @example + * const unit = getUnit('C'); + */ +function getUnit(zwaveUnit) { + switch (zwaveUnit) { + case 'C': + return DEVICE_FEATURE_UNITS.CELSIUS; + case 'F': + return DEVICE_FEATURE_UNITS.FAHRENHEIT; + case '%': + return DEVICE_FEATURE_UNITS.PERCENT; + case 'lux': + return DEVICE_FEATURE_UNITS.LUX; + default: + return null; + } +} + +module.exports = { + getUnit, +}; diff --git a/server/test/services/zwave/lib/nodesData.json b/server/test/services/zwave/lib/nodesData.json new file mode 100644 index 0000000000..9dcee42fbb --- /dev/null +++ b/server/test/services/zwave/lib/nodesData.json @@ -0,0 +1,1605 @@ +{ + "1": { + "manufacturer": "Z-Wave.Me", + "manufacturerid": "0x0115", + "product": "ZME_UZB1 USB Stick", + "producttype": "0x0400", + "productid": "0x0001", + "type": "Static PC Controller", + "name": "", + "loc": "", + "classes": { + "32": { + "0": { + "value_id": "1-32-1-0", + "node_id": 1, + "class_id": 32, + "type": "byte", + "genre": "basic", + "instance": 1, + "index": 0, + "label": "Basic", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + } + }, + "ready": true + }, + "2": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "37": { + "0": { + "value_id": "2-37-1-0", + "node_id": 2, + "class_id": 37, + "type": "bool", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Switch", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": false + } + }, + "39": { + "0": { + "value_id": "2-39-1-0", + "node_id": 2, + "class_id": 39, + "type": "list", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Switch All", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Disabled", "Off Enabled", "On Enabled", "On and Off Enabled"], + "value": "Disabled" + } + } + }, + "ready": false + }, + "3": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "37": { + "0": { + "value_id": "3-37-1-0", + "node_id": 3, + "class_id": 37, + "type": "bool", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Switch", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": false + } + }, + "39": { + "0": { + "value_id": "3-39-1-0", + "node_id": 3, + "class_id": 39, + "type": "list", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Switch All", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Disabled", "Off Enabled", "On Enabled", "On and Off Enabled"], + "value": "Disabled" + } + } + }, + "ready": false + }, + "4": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "37": { + "0": { + "value_id": "4-37-1-0", + "node_id": 4, + "class_id": 37, + "type": "bool", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Switch", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": false + } + }, + "39": { + "0": { + "value_id": "4-39-1-0", + "node_id": 4, + "class_id": 39, + "type": "list", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Switch All", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Disabled", "Off Enabled", "On Enabled", "On and Off Enabled"], + "value": "Disabled" + } + } + }, + "ready": false + }, + "5": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "32": { + "0": { + "value_id": "5-32-1-0", + "node_id": 5, + "class_id": 32, + "type": "byte", + "genre": "basic", + "instance": 1, + "index": 0, + "label": "Basic", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + }, + "132": { + "0": { + "value_id": "5-132-1-0", + "node_id": 5, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 3600 + } + } + }, + "ready": false + }, + "6": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "32": { + "0": { + "value_id": "6-32-1-0", + "node_id": 6, + "class_id": 32, + "type": "byte", + "genre": "basic", + "instance": 1, + "index": 0, + "label": "Basic", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + }, + "132": { + "0": { + "value_id": "6-132-1-0", + "node_id": 6, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 3600 + } + } + }, + "ready": false + }, + "7": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "32": { + "0": { + "value_id": "7-32-1-0", + "node_id": 7, + "class_id": 32, + "type": "byte", + "genre": "basic", + "instance": 1, + "index": 0, + "label": "Basic", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + }, + "132": { + "0": { + "value_id": "7-132-1-0", + "node_id": 7, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 3600 + } + } + }, + "ready": false + }, + "8": { + "manufacturer": "", + "manufacturerid": "", + "product": "", + "producttype": "", + "productid": "", + "type": "", + "name": "", + "loc": "", + "classes": { + "48": { + "0": { + "value_id": "8-48-1-0", + "node_id": 8, + "class_id": 48, + "type": "bool", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Sensor", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": false + } + }, + "132": { + "0": { + "value_id": "8-132-1-0", + "node_id": 8, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 3600 + } + } + }, + "ready": false + }, + "15": { + "manufacturer": "FIBARO System", + "manufacturerid": "0x010f", + "product": "FGMS001-ZW5 Motion Sensor", + "producttype": "0x0801", + "productid": "0x1001", + "type": "Home Security Sensor", + "name": "", + "loc": "", + "classes": { + "32": { + "0": { + "value_id": "15-32-1-0", + "node_id": 15, + "class_id": 32, + "type": "byte", + "genre": "basic", + "instance": 1, + "index": 0, + "label": "Basic", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + }, + "48": { + "0": { + "value_id": "15-48-1-0", + "node_id": 15, + "class_id": 48, + "type": "bool", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Sensor", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": false + } + }, + "49": { + "1": { + "value_id": "15-49-1-1", + "node_id": 15, + "class_id": 49, + "type": "decimal", + "genre": "user", + "instance": 1, + "index": 1, + "label": "Temperature", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "0.0" + }, + "3": { + "value_id": "15-49-1-3", + "node_id": 15, + "class_id": 49, + "type": "decimal", + "genre": "user", + "instance": 1, + "index": 3, + "label": "Luminance", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "0.0" + }, + "25": { + "value_id": "15-49-1-25", + "node_id": 15, + "class_id": 49, + "type": "decimal", + "genre": "user", + "instance": 1, + "index": 25, + "label": "Seismic Intensity", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "0.0" + } + }, + "94": { + "0": { + "value_id": "15-94-1-0", + "node_id": 15, + "class_id": 94, + "type": "byte", + "genre": "system", + "instance": 1, + "index": 0, + "label": "ZWave+ Version", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "1": { + "value_id": "15-94-1-1", + "node_id": 15, + "class_id": 94, + "type": "short", + "genre": "system", + "instance": 1, + "index": 1, + "label": "InstallerIcon", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": -32768, + "max": 32767, + "is_polled": false, + "value": 0 + }, + "2": { + "value_id": "15-94-1-2", + "node_id": 15, + "class_id": 94, + "type": "short", + "genre": "system", + "instance": 1, + "index": 2, + "label": "UserIcon", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": -32768, + "max": 32767, + "is_polled": false, + "value": 0 + } + }, + "112": { + "1": { + "value_id": "15-112-1-1", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 1, + "label": "Motion detection - sensitivity", + "units": "", + "help": "The lower the value, the more sensitive the PIR sensor is. Available settings: 8 - 255 Default setting: 15", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 15 + }, + "2": { + "value_id": "15-112-1-2", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 2, + "label": "Motion detection - blind time", + "units": "", + "help": "PIR sensor is blind (insensitive) to motion after last detection for the amount of time specified in this parameter. Shorter time periods allow to detect motion more frequently, but the battery will be drained faster. Available settings: 0 - 15 (0.5-8 seconds). Formula to calculate the time: time [s] = 0.5 x (value+1)) Default setting: 3", + "read_only": false, + "write_only": false, + "min": 0, + "max": 15, + "is_polled": false, + "value": 3 + }, + "3": { + "value_id": "15-112-1-3", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 3, + "label": "Motion detection - pulse counter", + "units": "", + "help": "This parameter determines the number of moves required for the PIR sensor to report motion. The higher the value, the less sensitive the PIR sensor is. It is not recommended to modify this parameter settings! Default setting: 1 (2 pulses)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 3, + "is_polled": false, + "values": ["1 pulse", "2 pulses", "3 pulses", "4 pulses"], + "value": "2 pulses" + }, + "4": { + "value_id": "15-112-1-4", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 4, + "label": "Motion detection - window time", + "units": "", + "help": "Period of time during which the number of moves set in parameter 3 must be detected in order for the PIR sensor to report motion. The higher the value, the more sensitive the PIR sensor is. It is not recommended to modify this parameter setting! Default setting: 2 (12 seconds)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 3, + "is_polled": false, + "values": ["4 seconds", "8 seconds", "12 seconds", "16 seconds"], + "value": "12 seconds" + }, + "6": { + "value_id": "15-112-1-6", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 6, + "label": "Motion detection - alarm cancellation delay", + "units": "seconds", + "help": "Time period after which the motion alarm will be cancelled in the main controller and associated devices. Any motion detected during this period resets the timer. Available settings: 1 - 65535 Default setting: 30 (30 seconds)", + "read_only": false, + "write_only": false, + "min": 1, + "max": 65535, + "is_polled": false, + "value": 30 + }, + "8": { + "value_id": "15-112-1-8", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 8, + "label": "Motion detection - operating mode", + "units": "", + "help": "This parameter determines in which part of day the PIR sensor will be active. This parameter influences only the motion reports and associations. Tamper, light intensity and temperature measurements will be still active, regardless of this parameter settings. Default setting: 0 (always active)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 2, + "is_polled": false, + "values": [ + "PIR sensor always active", + "PIR sensor active during the day only", + "PIR sensor active during the night only" + ], + "value": "PIR sensor always active" + }, + "9": { + "value_id": "15-112-1-9", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 9, + "label": "Motion detection - night/day", + "units": "lux", + "help": "This parameter defines the difference between night and day in terms of light intensity, used in parameter 8. Available settings: 1 - 65535 Default setting: 200 (200 lux)", + "read_only": false, + "write_only": false, + "min": 1, + "max": 65535, + "is_polled": false, + "value": 200 + }, + "12": { + "value_id": "15-112-1-12", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 12, + "label": "BASIC command class configuration", + "units": "", + "help": "This parameter determines the command frames sent to 2nd association group (assigned to PIR sensor). Default setting: 0 (ON and OFF)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 2, + "is_polled": false, + "values": ["BASIC On and OFF", "Only the BASIC On", "Only the BASIC OFF"], + "value": "BASIC On and OFF" + }, + "14": { + "value_id": "15-112-1-14", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 14, + "label": "BASIC ON command frame value", + "units": "", + "help": "The command frame sent at the moment of motion detection. Further motion detections, during the cancellation time, will not result in sending the association. Available settings: 0 - 255 Default setting: 255", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 255 + }, + "16": { + "value_id": "15-112-1-16", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 16, + "label": "BASIC OFF command frame value", + "units": "", + "help": "The command frame sent at the moment of motion alarm cancellation, after cancellation delay time specified in parameter 6. Available settings: 0 - 255 Default setting: 0", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "18": { + "value_id": "15-112-1-18", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 18, + "label": "Associations in Z-Wave network security mode", + "units": "", + "help": "This parameter defines how commands are sent in specified association groups: as secure or non-secure. Parameter is active only in Z-Wave network security mode. It does not apply to 1st group Lifeline. Available settings 0 - 15: 0 - none of the groups sent as secure 1 - 2nd group sent as secure. 2 - 3rd group sent as secure. 4 - 4th group sent as secure. 8 - 5th group sent as secure.", + "read_only": false, + "write_only": false, + "min": 0, + "max": 15, + "is_polled": false, + "value": 15 + }, + "20": { + "value_id": "15-112-1-20", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 20, + "label": "Tamper - sensitivity", + "units": "", + "help": "This parameter determines the change in force acting on the device, that will result in reporting tamper alarm - g-force acceleration. Available settings: 0 - 121 (0.08 - 2g; multiply by 0.016g; 0 = tamper inactive) Default setting: 20 (0.4g)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 121, + "is_polled": false, + "value": 20 + }, + "22": { + "value_id": "15-112-1-22", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 22, + "label": "Tamper - alarm cancellation delay", + "units": "seconds", + "help": "Time period after which a tamper alarm will be cancelled in the main controller and associated devices. Any tampering detected during this period will not extend the delay. Available settings: 1 - 65535 Default setting: 30 (seconds)", + "read_only": false, + "write_only": false, + "min": 1, + "max": 32767, + "is_polled": false, + "value": 30 + }, + "24": { + "value_id": "15-112-1-24", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 24, + "label": "Tamper - operating modes", + "units": "", + "help": "This parameter determines function of the tamper and sent reports. It is an advanced feature serving much more functions than just detection of tampering. Default setting: 0 (Tamper)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 2, + "is_polled": false, + "values": ["Tamper only", "Tamper and earthquake detector", "Tamper and orientation in space"], + "value": "Tamper only" + }, + "25": { + "value_id": "15-112-1-25", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 25, + "label": "Tamper - alarm cancellation", + "units": "", + "help": "This parameter allows to disable cancellation of the tamper alarm. Default setting: 0 (Tamper).", + "read_only": false, + "write_only": false, + "min": 0, + "max": 1, + "is_polled": false, + "values": ["Do not send tamper cancellation report", "Send tamper cancellation report"], + "value": "Send tamper cancellation report" + }, + "28": { + "value_id": "15-112-1-28", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 28, + "label": "Tamper alarm broadcast mode", + "units": "", + "help": "The parameter determines whether the tamper alarm frame will or will not be sent in broadcast mode. Alarm frames sent in broadcast mode can be received by all of the devices within range (if they accept such frames), but not repeated by them. Default setting: 0", + "read_only": false, + "write_only": false, + "min": 0, + "max": 1, + "is_polled": false, + "values": ["Tamper alarm sent to 3rd association group", "Tamper alarm sent in broadcast mode"], + "value": "Tamper alarm sent to 3rd association group" + }, + "29": { + "value_id": "15-112-1-29", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 29, + "label": "Tamper - backward compatible broadcast mode", + "units": "", + "help": "The parameter determines whether the backward compatible tamper alarm frame will or will not be sent in broadcast mode. Alarm frames sent in broadcast mode can be received by all of the devices within range (if they accept such frames), but not repeated by them. This parameter provides backward compatibility with controllers not supporting Z-Wave+. Default setting: 0", + "read_only": false, + "write_only": false, + "min": 0, + "max": 1, + "is_polled": false, + "values": [ + "Backward compatible tamper alarm sent to 5th association group", + "Backward compatible tamper alarm sent in broadcast mode" + ], + "value": "Backward compatible tamper alarm sent to 5th association group" + }, + "40": { + "value_id": "15-112-1-40", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 40, + "label": "Luminance report - threshold", + "units": "lux", + "help": "This parameter determines the change in light intensity level resulting in luminance report being sent to the main controller. Available settings: 0 - reports are not sent. 1-32767 (luminance in lux). Default setting: 200 (200 lux).", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 200 + }, + "42": { + "value_id": "15-112-1-42", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 42, + "label": "Luminance reports - interval", + "units": "seconds", + "help": "Time interval between consecutive luminance reports. The reports are sent even if there is no change in the light intensity. Available settings: 0 - 32767. 0 - periodical reports are not sent. 1-32767 (in seconds).", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 0 + }, + "60": { + "value_id": "15-112-1-60", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 60, + "label": "Temperature report - threshold", + "units": "", + "help": "This parameter determines the change in measured temperature that will result in new temperature report being sent to the main controller. Available settings: 0 - 255 (0.1 - 25.5C; 0 = reports are not sent) Default setting: 10 (1C)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 10 + }, + "62": { + "value_id": "15-112-1-62", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 62, + "label": "Temperature measuring - interval", + "units": "seconds", + "help": "Time interval between consecutive temperature measurements. The shorter the time, the more frequently the temperature will be measured, but the battery life will shorten. Available settings: 0 - 32767 (1 - 32767 seconds; 0 = temperature is not measured) Default setting: 900 (900 seconds).", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 900 + }, + "64": { + "value_id": "15-112-1-64", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 64, + "label": "Temperature report - interval", + "units": "seconds", + "help": "Time interval between consecutive temperature reports. The reports are sent even if there is no change in the temperature. Available settings: 0 - 32767 (1 - 32767 seconds; 0 = periodical reports are not sent) Default setting: 0", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 0 + }, + "66": { + "value_id": "15-112-1-66", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 66, + "label": "Temperature offset", + "units": "", + "help": "The value to be added to the actual temperature, measured by the sensor (temperature compensation). Available settings: 0 - 100 (0 to 100C) or 64536 - 65535 (-100 to -0.10C) Default setting: 0", + "read_only": false, + "write_only": false, + "min": 0, + "max": 65535, + "is_polled": false, + "value": 0 + }, + "80": { + "value_id": "15-112-1-80", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 80, + "label": "Visual LED indicator - signalling mode", + "units": "", + "help": "This parameter determines the way in which visual indicator behaves after motion has been detected. Default setting: 10 (Flashlight mode)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 26, + "is_polled": false, + "values": [ + "LED inactive.", + "1 long blink, LED colour depends on the temperature. Set by parameters 86 and 87.", + "Flashlight mode - LED glows in white for 10 seconds.", + "Long blink White.", + "Long blink Red.", + "Long blink Green.", + "Long blink Blue.", + "Long blink Yellow.", + "Long blink Cyan.", + "Long blink Magenta.", + "Long blink, then short blink, LED colour depends on the temperature. Set by parameters 86 and 87.", + "Flashlight mode - LED glows in white through 10 seconds. Each next detected motion extends the glowing by next 10 seconds.", + "Long blink, then short blinks White.", + "Long blink, then short blinks Red.", + "Long blink, then short blinks Green.", + "Long blink, then short blinks Blue.", + "Long blink, then short blinks Yellow.", + "Long blink, then short blinks Cyan", + "Long blink, then short blinks Magenta", + "Long blink, then 2 short blinks, LED colour depends on the temperature. Set by parameters 86 and 87.", + "Long blink, then 2 short blinks White", + "Long blink, then 2 short blinks Red", + "Long blink, then 2 short blinks Green", + "Long blink, then 2 short blinks Blue", + "Long blink, then 2 short blinks Yellow", + "Long blink, then 2 short blinks Cyan", + "Long blink, then 2 short blinks Magenta" + ], + "value": "Long blink, then short blink, LED colour depends on the temperature. Set by parameters 86 and 87." + }, + "81": { + "value_id": "15-112-1-81", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 81, + "label": "Visual LED indicator - brightness", + "units": "%", + "help": "This parameter determines the brightness of the visual LED indicator when indicating motion. Available settings: 0 - brightness determined by the luminance (parameters 82 and 83). 1-100 (1-100%) Default setting: 50 (50 %)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 100, + "is_polled": false, + "value": 50 + }, + "82": { + "value_id": "15-112-1-82", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 82, + "label": "Visual LED indicator - luminance for low indicator brightness", + "units": "lux", + "help": "Light intensity level below which brightness of visual indicator is set to 1%. Available settings: 0 to value of parameter 83 (in lux). Default setting: 100.", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 100 + }, + "83": { + "value_id": "15-112-1-83", + "node_id": 15, + "class_id": 112, + "type": "short", + "genre": "config", + "instance": 1, + "index": 83, + "label": "Visual LED indicator - luminance for high indicator brightness", + "units": "lux", + "help": "Light intensity level above which brightness of visual indicator is set to 100%. Available settings: value of parameter 82 to 32767 (in lux) Default setting: 1000 (1000 lux) NOTE The value of the parameter 83 must be higher than the value of the parameter 82.", + "read_only": false, + "write_only": false, + "min": 0, + "max": 32767, + "is_polled": false, + "value": 1000 + }, + "86": { + "value_id": "15-112-1-86", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 86, + "label": "Visual LED indicator - temperature for blue colour", + "units": "Celsius", + "help": "This parameter is determines minimal temperature that will result in blue visual indicator colour. Relevant only when parameter 80 has been properly configured. Available settings: 0 to value of parameter 87 (in Celsius degree) Default setting: 18 (18C)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 18 + }, + "87": { + "value_id": "15-112-1-87", + "node_id": 15, + "class_id": 112, + "type": "byte", + "genre": "config", + "instance": 1, + "index": 87, + "label": "Visual LED indicator - temperature for red colour", + "units": "Celsius", + "help": "This parameter is determines minimal temperature that will result in red visual indicator colour. Relevant only when parameter 80 has been properly configured. Available settings: value of parameter 86 to 255 (in Celsius degree) Default setting: 28 (28C)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 28 + }, + "89": { + "value_id": "15-112-1-89", + "node_id": 15, + "class_id": 112, + "type": "list", + "genre": "config", + "instance": 1, + "index": 89, + "label": "Visual LED indicator - tamper alarm", + "units": "", + "help": "Indicating mode resembles a police car (white, red and blue). Default setting: 1 (on)", + "read_only": false, + "write_only": false, + "min": 0, + "max": 1, + "is_polled": false, + "values": ["Tamper alarm is not indicated", "Tamper alarm is indicated"], + "value": "Tamper alarm is indicated" + } + }, + "113": { + "0": { + "value_id": "15-113-1-0", + "node_id": 15, + "class_id": 113, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Alarm Type", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "1": { + "value_id": "15-113-1-1", + "node_id": 15, + "class_id": 113, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 1, + "label": "Alarm Level", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "2": { + "value_id": "15-113-1-2", + "node_id": 15, + "class_id": 113, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 2, + "label": "SourceNodeId", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "10": { + "value_id": "15-113-1-10", + "node_id": 15, + "class_id": 113, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 10, + "label": "Burglar", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + }, + "115": { + "0": { + "value_id": "15-115-1-0", + "node_id": 15, + "class_id": 115, + "type": "list", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Powerlevel", + "units": "dB", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Normal", "-1dB", "-2dB", "-3dB", "-4dB", "-5dB", "-6dB", "-7dB", "-8dB", "-9dB"], + "value": "Normal" + }, + "1": { + "value_id": "15-115-1-1", + "node_id": 15, + "class_id": 115, + "type": "byte", + "genre": "system", + "instance": 1, + "index": 1, + "label": "Timeout", + "units": "seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "2": { + "value_id": "15-115-1-2", + "node_id": 15, + "class_id": 115, + "type": "button", + "genre": "system", + "instance": 1, + "index": 2, + "label": "Set Powerlevel", + "units": "", + "help": "", + "read_only": false, + "write_only": true, + "min": 0, + "max": 0, + "is_polled": false + }, + "3": { + "value_id": "15-115-1-3", + "node_id": 15, + "class_id": 115, + "type": "byte", + "genre": "system", + "instance": 1, + "index": 3, + "label": "Test Node", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + }, + "4": { + "value_id": "15-115-1-4", + "node_id": 15, + "class_id": 115, + "type": "list", + "genre": "system", + "instance": 1, + "index": 4, + "label": "Test Powerlevel", + "units": "dB", + "help": "", + "read_only": false, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Normal", "-1dB", "-2dB", "-3dB", "-4dB", "-5dB", "-6dB", "-7dB", "-8dB", "-9dB"], + "value": "Normal" + }, + "5": { + "value_id": "15-115-1-5", + "node_id": 15, + "class_id": 115, + "type": "short", + "genre": "system", + "instance": 1, + "index": 5, + "label": "Frame Count", + "units": "", + "help": "", + "read_only": false, + "write_only": false, + "min": -32768, + "max": 32767, + "is_polled": false, + "value": 0 + }, + "6": { + "value_id": "15-115-1-6", + "node_id": 15, + "class_id": 115, + "type": "button", + "genre": "system", + "instance": 1, + "index": 6, + "label": "Test", + "units": "", + "help": "", + "read_only": false, + "write_only": true, + "min": 0, + "max": 0, + "is_polled": false + }, + "7": { + "value_id": "15-115-1-7", + "node_id": 15, + "class_id": 115, + "type": "button", + "genre": "system", + "instance": 1, + "index": 7, + "label": "Report", + "units": "", + "help": "", + "read_only": false, + "write_only": true, + "min": 0, + "max": 0, + "is_polled": false + }, + "8": { + "value_id": "15-115-1-8", + "node_id": 15, + "class_id": 115, + "type": "list", + "genre": "system", + "instance": 1, + "index": 8, + "label": "Test Status", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "values": ["Failed", "Success", "In Progress"], + "value": "Failed" + }, + "9": { + "value_id": "15-115-1-9", + "node_id": 15, + "class_id": 115, + "type": "short", + "genre": "system", + "instance": 1, + "index": 9, + "label": "Acked Frames", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": -32768, + "max": 32767, + "is_polled": false, + "value": 0 + } + }, + "128": { + "0": { + "value_id": "15-128-1-0", + "node_id": 15, + "class_id": 128, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 0, + "label": "Battery Level", + "units": "%", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 100 + } + }, + "132": { + "0": { + "value_id": "15-132-1-0", + "node_id": 15, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": false, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 3600 + }, + "1": { + "value_id": "15-132-1-1", + "node_id": 15, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 1, + "label": "Minimum Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": true, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 0 + }, + "2": { + "value_id": "15-132-1-2", + "node_id": 15, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 2, + "label": "Maximum Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": true, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 0 + }, + "3": { + "value_id": "15-132-1-3", + "node_id": 15, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 3, + "label": "Default Wake-up Interval", + "units": "Seconds", + "help": "", + "read_only": true, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 0 + }, + "4": { + "value_id": "15-132-1-4", + "node_id": 15, + "class_id": 132, + "type": "int", + "genre": "system", + "instance": 1, + "index": 4, + "label": "Wake-up Interval Step", + "units": "Seconds", + "help": "", + "read_only": true, + "write_only": false, + "min": -2147483648, + "max": 2147483647, + "is_polled": false, + "value": 0 + } + }, + "134": { + "0": { + "value_id": "15-134-1-0", + "node_id": 15, + "class_id": 134, + "type": "string", + "genre": "system", + "instance": 1, + "index": 0, + "label": "Library Version", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "Unknown" + }, + "1": { + "value_id": "15-134-1-1", + "node_id": 15, + "class_id": 134, + "type": "string", + "genre": "system", + "instance": 1, + "index": 1, + "label": "Protocol Version", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "Unknown" + }, + "2": { + "value_id": "15-134-1-2", + "node_id": 15, + "class_id": 134, + "type": "string", + "genre": "system", + "instance": 1, + "index": 2, + "label": "Application Version", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 0, + "is_polled": false, + "value": "Unknown" + } + }, + "156": { + "0": { + "value_id": "15-156-1-0", + "node_id": 15, + "class_id": 156, + "type": "byte", + "genre": "user", + "instance": 1, + "index": 0, + "label": "General", + "units": "", + "help": "", + "read_only": true, + "write_only": false, + "min": 0, + "max": 255, + "is_polled": false, + "value": 0 + } + } + }, + "ready": true + } +} diff --git a/server/test/services/zwave/lib/zwaveManager.test.js b/server/test/services/zwave/lib/zwaveManager.test.js index 3397841d4d..0720acaebf 100644 --- a/server/test/services/zwave/lib/zwaveManager.test.js +++ b/server/test/services/zwave/lib/zwaveManager.test.js @@ -5,6 +5,7 @@ const EventEmitter = require('events'); const event = new EventEmitter(); const ZwaveManager = require('../../../../services/zwave/lib'); const ZwaveMock = require('../ZwaveMock.test'); +const nodesData = require('./nodesData.json'); describe('zwaveManager commands', () => { const zwaveManager = new ZwaveManager(ZwaveMock, event, 'de051f90-f34a-4fd5-be2e-e502339ec9bc'); @@ -51,6 +52,7 @@ describe('zwaveManager commands', () => { }); }); it('should return array of nodes', () => { + zwaveManager.nodes = nodesData; const nodes = zwaveManager.getNodes(); expect(nodes).to.be.instanceOf(Array); }); @@ -87,6 +89,9 @@ describe('zwaveManager events', () => { it('should receive node added', () => { zwaveManager.nodeAdded(1); }); + it('should receive node removed', () => { + zwaveManager.nodeRemoved(2); + }); it('should receive node ready info', () => { zwaveManager.nodeReady(1, { manufacturer: 'Aeotec', diff --git a/server/utils/constants.js b/server/utils/constants.js index 080579c7fd..adab398e9d 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -343,6 +343,8 @@ const WEBSOCKET_MESSAGE_TYPES = { DRIVER_FAILED: 'zwave.driver-failed', NODE_READY: 'zwave.node-ready', SCAN_COMPLETE: 'zwave.scan-complete', + NODE_ADDED: 'zwave.node-added', + NODE_REMOVED: 'zwave.node-removed', }, MQTT: { CONNECTED: 'mqtt.connected',