From d125248f03f45ee06b7f62418fde1584af321d79 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Mon, 1 May 2023 20:39:57 +0200 Subject: [PATCH 01/20] Initial commit --- .../actions/TurnOnOffLightParams.jsx | 38 ++++++++++-------- .../actions/TurnOnOffSwitchParams.jsx | 40 ++++++++++--------- server/lib/scene/scene.actions.js | 40 ++++++------------- 3 files changed, 55 insertions(+), 63 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 51a97d818f..29a109fda0 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx @@ -8,17 +8,21 @@ import { ACTIONS } from '../../../../../../server/utils/constants'; class TurnOnOffLight extends Component { getOptions = async () => { try { - const devices = await this.props.httpClient.get('/api/v1/device', { + const deviceFeatures = await this.props.httpClient.get('/api/v1/device', { device_feature_category: 'light', device_feature_type: 'binary' }); - const deviceOptions = devices.map(device => ({ - value: device.selector, - label: device.name - })); - await this.setState({ deviceOptions }); + const deviceFeatureOptions = deviceFeatures + .flatMap(device => ( + device.features + )) + .map(deviceFeature => ({ + value: deviceFeature.selector, + label: deviceFeature.name + })); + await this.setState({ deviceFeatureOptions }); this.refreshSelectedOptions(this.props); - return deviceOptions; + return deviceFeatureOptions; } catch (e) { console.error(e); } @@ -26,18 +30,18 @@ class TurnOnOffLight extends Component { handleChange = selectedOptions => { if (selectedOptions) { const lights = selectedOptions.map(selectedOption => selectedOption.value); - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', lights); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'device_features', lights); } else { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', []); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'device_features', []); } }; refreshSelectedOptions = nextProps => { const selectedOptions = []; - if (nextProps.action.devices && this.state.deviceOptions) { - nextProps.action.devices.forEach(light => { - const deviceOption = this.state.deviceOptions.find(deviceOption => deviceOption.value === light); - if (deviceOption) { - selectedOptions.push(deviceOption); + if (nextProps.action.device_features && this.state.deviceFeatureOptions) { + nextProps.action.device_features.forEach(light => { + const deviceFeatureOption = this.state.deviceFeatureOptions.find(deviceFeatureOption => deviceFeatureOption.value === light); + if (deviceFeatureOption) { + selectedOptions.push(deviceFeatureOption); } }); } @@ -46,7 +50,7 @@ class TurnOnOffLight extends Component { constructor(props) { super(props); this.state = { - deviceOptions: null, + deviceFeatureOptions: null, selectedOptions: [] }; } @@ -58,7 +62,7 @@ class TurnOnOffLight extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions }) { + render(props, { selectedOptions, deviceFeatureOptions }) { return (
); diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx index c485b41df1..41ff682aa4 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx @@ -8,22 +8,26 @@ import { ACTIONS } from '../../../../../../server/utils/constants'; class TurnOnOffSwitch extends Component { getOptions = async () => { try { - const devices = await this.props.httpClient.get('/api/v1/device', { + const deviceFeatures = await this.props.httpClient.get('/api/v1/device', { device_feature_category: 'switch', device_feature_type: 'binary' }); // keep only write switches, not read only - const devicesFiltered = devices.filter(device => { + const deviceFeaturesFiltered = deviceFeatures.filter(device => { const writeSwitch = device.features.find(f => f.read_only === false); return writeSwitch !== undefined; }); - const deviceOptions = devicesFiltered.map(device => ({ - value: device.selector, - label: device.name - })); - await this.setState({ deviceOptions }); + const deviceFeatureOptions = deviceFeaturesFiltered + .flatMap(device => ( + device.features + )) + .map(device_feature => ({ + value: device_feature.selector, + label: device_feature.name + })); + await this.setState({ deviceFeatureOptions }); this.refreshSelectedOptions(this.props); - return deviceOptions; + return deviceFeatureOptions; } catch (e) { console.error(e); } @@ -31,18 +35,18 @@ class TurnOnOffSwitch extends Component { handleChange = selectedOptions => { if (selectedOptions) { const switches = selectedOptions.map(selectedOption => selectedOption.value); - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', switches); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'device_features', switches); } else { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', []); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'device_features', []); } }; refreshSelectedOptions = nextProps => { const selectedOptions = []; - if (nextProps.action.devices && this.state.deviceOptions) { - nextProps.action.devices.forEach(switches => { - const deviceOption = this.state.deviceOptions.find(deviceOption => deviceOption.value === switches); - if (deviceOption) { - selectedOptions.push(deviceOption); + if (nextProps.action.device_features && this.state.deviceFeatureOptions) { + nextProps.action.device_features.forEach(switches => { + const deviceFeatureOption = this.state.deviceFeatureOptions.find(deviceFeatureOption => deviceFeatureOption.value === switches); + if (deviceFeatureOption) { + selectedOptions.push(deviceFeatureOption); } }); } @@ -51,7 +55,7 @@ class TurnOnOffSwitch extends Component { constructor(props) { super(props); this.state = { - deviceOptions: null, + deviceFeatureOptions: null, selectedOptions: [] }; } @@ -63,7 +67,7 @@ class TurnOnOffSwitch extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions }) { + render(props, { selectedOptions, deviceFeatureOptions }) { return (
); diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 4d3b7ac528..0d94308941 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -30,14 +30,10 @@ const actionsFunc = { return self.device.setValue(device, deviceFeature, action.value); }, [ACTIONS.LIGHT.TURN_ON]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.LIGHT, - DEVICE_FEATURE_TYPES.LIGHT.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); } catch (e) { logger.warn(e); @@ -45,14 +41,10 @@ const actionsFunc = { }); }, [ACTIONS.LIGHT.TURN_OFF]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.LIGHT, - DEVICE_FEATURE_TYPES.LIGHT.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); } catch (e) { logger.warn(e); @@ -75,14 +67,10 @@ const actionsFunc = { }); }, [ACTIONS.SWITCH.TURN_ON]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.SWITCH, - DEVICE_FEATURE_TYPES.SWITCH.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); } catch (e) { logger.warn(e); @@ -90,14 +78,10 @@ const actionsFunc = { }); }, [ACTIONS.SWITCH.TURN_OFF]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.SWITCH, - DEVICE_FEATURE_TYPES.SWITCH.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); } catch (e) { logger.warn(e); From 4f31c6c7f84e4fd53ea4a2c0f5b47dc824623024 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Mon, 1 May 2023 20:48:46 +0200 Subject: [PATCH 02/20] Test --- .../scene/edit-scene/actions/TurnOnOffLightParams.jsx | 8 ++++---- .../scene/edit-scene/actions/TurnOnOffSwitchParams.jsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 29a109fda0..272dd73193 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx @@ -13,9 +13,7 @@ class TurnOnOffLight extends Component { device_feature_type: 'binary' }); const deviceFeatureOptions = deviceFeatures - .flatMap(device => ( - device.features - )) + .flatMap(device => device.features) .map(deviceFeature => ({ value: deviceFeature.selector, label: deviceFeature.name @@ -39,7 +37,9 @@ class TurnOnOffLight extends Component { const selectedOptions = []; if (nextProps.action.device_features && this.state.deviceFeatureOptions) { nextProps.action.device_features.forEach(light => { - const deviceFeatureOption = this.state.deviceFeatureOptions.find(deviceFeatureOption => deviceFeatureOption.value === light); + const deviceFeatureOption = this.state.deviceFeatureOptions.find( + deviceFeatureOption => deviceFeatureOption.value === light + ); if (deviceFeatureOption) { selectedOptions.push(deviceFeatureOption); } diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx index 41ff682aa4..841f2213e3 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx @@ -18,9 +18,7 @@ class TurnOnOffSwitch extends Component { return writeSwitch !== undefined; }); const deviceFeatureOptions = deviceFeaturesFiltered - .flatMap(device => ( - device.features - )) + .flatMap(device => device.features) .map(device_feature => ({ value: device_feature.selector, label: device_feature.name @@ -44,7 +42,9 @@ class TurnOnOffSwitch extends Component { const selectedOptions = []; if (nextProps.action.device_features && this.state.deviceFeatureOptions) { nextProps.action.device_features.forEach(switches => { - const deviceFeatureOption = this.state.deviceFeatureOptions.find(deviceFeatureOption => deviceFeatureOption.value === switches); + const deviceFeatureOption = this.state.deviceFeatureOptions.find( + deviceFeatureOption => deviceFeatureOption.value === switches + ); if (deviceFeatureOption) { selectedOptions.push(deviceFeatureOption); } From ba6d2528f16981284e72f5752b14869be61fed56 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Tue, 2 May 2023 23:04:11 +0200 Subject: [PATCH 03/20] Tests --- server/lib/scene/scene.actions.js | 36 ++++++-------- .../scene/scene.executeSingleAction.test.js | 48 ++++++++++++++----- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 0d94308941..7f4598a3e8 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -30,9 +30,9 @@ const actionsFunc = { return self.device.setValue(device, deviceFeature, action.value); }, [ACTIONS.LIGHT.TURN_ON]: async (self, action, scope) => { - await Promise.map(action.device_features, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); } catch (e) { @@ -41,9 +41,9 @@ const actionsFunc = { }); }, [ACTIONS.LIGHT.TURN_OFF]: async (self, action, scope) => { - await Promise.map(action.device_features, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); } catch (e) { @@ -52,14 +52,10 @@ const actionsFunc = { }); }, [ACTIONS.LIGHT.TOGGLE]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.LIGHT, - DEVICE_FEATURE_TYPES.LIGHT.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); } catch (e) { logger.warn(e); @@ -67,9 +63,9 @@ const actionsFunc = { }); }, [ACTIONS.SWITCH.TURN_ON]: async (self, action, scope) => { - await Promise.map(action.device_features, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); } catch (e) { @@ -78,9 +74,9 @@ const actionsFunc = { }); }, [ACTIONS.SWITCH.TURN_OFF]: async (self, action, scope) => { - await Promise.map(action.device_features, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); } catch (e) { @@ -89,14 +85,10 @@ const actionsFunc = { }); }, [ACTIONS.SWITCH.TOGGLE]: async (self, action, scope) => { - await Promise.map(action.devices, async (deviceSelector) => { + await Promise.map(action.device_features, async (deviceFeatureSelector) => { try { - const device = self.stateManager.get('device', deviceSelector); - const deviceFeature = getDeviceFeature( - device, - DEVICE_FEATURE_CATEGORIES.SWITCH, - DEVICE_FEATURE_TYPES.SWITCH.BINARY, - ); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); } catch (e) { logger.warn(e); diff --git a/server/test/lib/scene/scene.executeSingleAction.test.js b/server/test/lib/scene/scene.executeSingleAction.test.js index 7fb3da1675..6f8dcbb57d 100644 --- a/server/test/lib/scene/scene.executeSingleAction.test.js +++ b/server/test/lib/scene/scene.executeSingleAction.test.js @@ -9,6 +9,8 @@ const event = new EventEmitter(); const deviceFeatureLightBinary = { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + device_id: 'light-1', + last_value: 0, }; const lightDevice = { @@ -18,6 +20,8 @@ const lightDevice = { const deviceFeatureSwitchBinary = { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, + device_id: 'switch-1', + last_value: 0, }; const switchDevice = { @@ -30,11 +34,12 @@ describe('scene.executeSingleAction', () => { setValue: fake.resolves(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'light-1', lightDevice); + stateManager.setState('deviceById', 'light-1', lightDevice); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeatureLightBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }); assert.calledWith(device.setValue, lightDevice, deviceFeatureLightBinary, 1); }); @@ -43,11 +48,12 @@ describe('scene.executeSingleAction', () => { setValue: fake.resolves(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'light-1', lightDevice); + stateManager.setState('deviceById', 'light-1', lightDevice); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeatureLightBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }); assert.calledWith(device.setValue, lightDevice, deviceFeatureLightBinary, 0); }); @@ -56,11 +62,26 @@ describe('scene.executeSingleAction', () => { setValue: fake.resolves(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'switch-1', switchDevice); + stateManager.setState('deviceById', 'light-1', lightDevice); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeatureLightBinary); + const sceneManager = new SceneManager(stateManager, event, device); + await sceneManager.executeSingleAction({ + type: ACTIONS.LIGHT.TOGGLE, + device_features: ['light-1-binary'], + }); + assert.calledWith(device.setValue, lightDevice, deviceFeatureLightBinary, 1); + }); + it('should execute one action', async () => { + const device = { + setValue: fake.resolves(null), + }; + const stateManager = new StateManager(); + stateManager.setState('deviceById', 'switch-1', switchDevice); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeatureSwitchBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.SWITCH.TURN_ON, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }); assert.calledWith(device.setValue, switchDevice, deviceFeatureSwitchBinary, 1); }); @@ -69,11 +90,12 @@ describe('scene.executeSingleAction', () => { setValue: fake.resolves(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'switch-1', switchDevice); + stateManager.setState('deviceById', 'switch-1', switchDevice); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeatureSwitchBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.SWITCH.TURN_OFF, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }); assert.calledWith(device.setValue, switchDevice, deviceFeatureSwitchBinary, 0); }); @@ -82,11 +104,12 @@ describe('scene.executeSingleAction', () => { setValue: fake.rejects(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'switch-1', switchDevice); + stateManager.setState('deviceById', 'switch-1', switchDevice); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeatureSwitchBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.SWITCH.TURN_OFF, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }); assert.calledWith(device.setValue, switchDevice, deviceFeatureSwitchBinary, 0); }); @@ -95,11 +118,12 @@ describe('scene.executeSingleAction', () => { setValue: fake.rejects(null), }; const stateManager = new StateManager(); - stateManager.setState('device', 'switch-1', switchDevice); + stateManager.setState('deviceById', 'switch-1', switchDevice); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeatureSwitchBinary); const sceneManager = new SceneManager(stateManager, event, device); await sceneManager.executeSingleAction({ type: ACTIONS.SWITCH.TURN_ON, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }); assert.calledWith(device.setValue, switchDevice, deviceFeatureSwitchBinary, 1); }); From 53543414aec3336beb97569c8d79edc938a209b8 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Tue, 2 May 2023 23:16:49 +0200 Subject: [PATCH 04/20] Prettier --- server/lib/scene/scene.actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 7f4598a3e8..86c6d344a0 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -6,7 +6,7 @@ const get = require('get-value'); const dayjs = require('dayjs'); const utc = require('dayjs/plugin/utc'); const timezone = require('dayjs/plugin/timezone'); -const { ACTIONS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../utils/constants'); +const { ACTIONS } = require('../../utils/constants'); const { getDeviceFeature } = require('../../utils/device'); const { AbortScene } = require('../../utils/coreErrors'); const { compare } = require('../../utils/compare'); From ec2d327ad26fd649e4c1dc3378039b1c5d94ba53 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Wed, 3 May 2023 10:51:45 +0200 Subject: [PATCH 05/20] Tests --- server/lib/scene/scene.actions.js | 36 ++--- .../lib/scene/scene.executeActions.test.js | 125 +++++++----------- 2 files changed, 58 insertions(+), 103 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 86c6d344a0..e0e7577ebf 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -31,67 +31,55 @@ const actionsFunc = { }, [ACTIONS.LIGHT.TURN_ON]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); - } catch (e) { - logger.warn(e); } }); }, [ACTIONS.LIGHT.TURN_OFF]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); - } catch (e) { - logger.warn(e); } }); }, [ACTIONS.LIGHT.TOGGLE]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); - } catch (e) { - logger.warn(e); } }); }, [ACTIONS.SWITCH.TURN_ON]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 1); - } catch (e) { - logger.warn(e); } }); }, [ACTIONS.SWITCH.TURN_OFF]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, 0); - } catch (e) { - logger.warn(e); } }); }, [ACTIONS.SWITCH.TOGGLE]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - try { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { const device = self.stateManager.get('deviceById', deviceFeature.device_id); await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); - } catch (e) { - logger.warn(e); } }); }, diff --git a/server/test/lib/scene/scene.executeActions.test.js b/server/test/lib/scene/scene.executeActions.test.js index 653effe8be..2918cfd53a 100644 --- a/server/test/lib/scene/scene.executeActions.test.js +++ b/server/test/lib/scene/scene.executeActions.test.js @@ -18,24 +18,25 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + device_id: 'light-1', }; const device = { setValue: fake.resolves(null), features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'light-1', device); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -47,24 +48,25 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + device_id: 'light-1', }; const device = { setValue: fake.resolves(null), features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'light-1', device); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -76,6 +78,7 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + device_id: 'light-1', last_value: 0, }; const device = { @@ -83,18 +86,18 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'light-1', device); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.LIGHT.TOGGLE, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -106,6 +109,7 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + device_id: 'light-1', last_value: 1, }; const device = { @@ -113,18 +117,18 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'light-1', device); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.LIGHT.TOGGLE, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -132,53 +136,29 @@ describe('scene.executeActions', () => { ); assert.calledOnceWithExactly(device.setValue, device, deviceFeature, 0); }); - it('should execute light toggle error', async () => { - const device = { - setValue: fake.resolves(null), - features: { - category: DEVICE_FEATURE_CATEGORIES.LIGHT, - type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.throws('An error occured'), - }, - }; - const stateManager = new StateManager(event); - stateManager.setState('device', 'light-1', device); - await executeActions( - { stateManager, event, device }, - [ - [ - { - type: ACTIONS.LIGHT.TOGGLE, - devices: ['light-1'], - }, - ], - ], - {}, - ); - assert.notCalled(device.setValue); - }); it('should execute switch turn on', async () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, + device_id: 'switch-1', }; const device = { setValue: fake.resolves(null), features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'switch-1', device); + stateManager.setState('deviceById', 'switch-1', device); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.SWITCH.TURN_ON, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], ], @@ -190,24 +170,25 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, + device_id: 'switch-1', }; const device = { setValue: fake.resolves(null), features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'switch-1', device); + stateManager.setState('deviceById', 'switch-1', device); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.SWITCH.TURN_OFF, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], ], @@ -219,6 +200,7 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, + device_id: 'switch-1', last_value: 0, }; const device = { @@ -226,18 +208,18 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'switch-1', device); + stateManager.setState('deviceById', 'switch-1', device); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.SWITCH.TOGGLE, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], ], @@ -249,6 +231,7 @@ describe('scene.executeActions', () => { const deviceFeature = { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, + device_id: 'switch-1', last_value: 1, }; const device = { @@ -256,18 +239,18 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; const stateManager = new StateManager(event); - stateManager.setState('device', 'switch-1', device); + stateManager.setState('deviceById', 'switch-1', device); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.SWITCH.TURN_OFF, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], ], @@ -275,31 +258,6 @@ describe('scene.executeActions', () => { ); assert.calledOnceWithExactly(device.setValue, device, deviceFeature, 0); }); - it('should execute switch toggle error', async () => { - const device = { - setValue: fake.resolves(null), - features: { - category: DEVICE_FEATURE_CATEGORIES.SWITCH, - type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.throws('An error occured'), - }, - }; - const stateManager = new StateManager(event); - stateManager.setState('device', 'switch-1', device); - await executeActions( - { stateManager, event, device }, - [ - [ - { - type: ACTIONS.SWITCH.TOGGLE, - devices: ['switch-1'], - }, - ], - ], - {}, - ); - assert.notCalled(device.setValue); - }); it('should execute wait 5 ms', async () => { await executeActions( { event }, @@ -358,32 +316,41 @@ describe('scene.executeActions', () => { const device = { setValue: fake.resolves(null), }; + const deviceFeatureLight = { + device_id: 'device', + }; + const deviceFeatureSwitch = { + device_id: 'device', + }; const stateManager = new StateManager(event); + stateManager.setState('deviceById', 'device', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeatureLight); + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeatureSwitch); await executeActions( { stateManager, event, device }, [ [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], [ { type: ACTIONS.SWITCH.TURN_ON, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], [ { type: ACTIONS.SWITCH.TURN_OFF, - devices: ['switch-1'], + device_features: ['switch-1-binary'], }, ], ], @@ -391,7 +358,7 @@ describe('scene.executeActions', () => { ); assert.callCount(device.setValue, 4); }); - it('should throw error, action type does not exist', async () => { + it.only('should throw error, action type does not exist', async () => { const light = { turnOn: fake.resolves(null), }; From 0aed666a4e276f0d1cbcf68add63950e8e33b0c9 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Wed, 3 May 2023 11:22:20 +0200 Subject: [PATCH 06/20] Tests --- server/test/lib/scene/scene.executeActions.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test/lib/scene/scene.executeActions.test.js b/server/test/lib/scene/scene.executeActions.test.js index 2918cfd53a..b0f0beac6c 100644 --- a/server/test/lib/scene/scene.executeActions.test.js +++ b/server/test/lib/scene/scene.executeActions.test.js @@ -358,7 +358,7 @@ describe('scene.executeActions', () => { ); assert.callCount(device.setValue, 4); }); - it.only('should throw error, action type does not exist', async () => { + it('should throw error, action type does not exist', async () => { const light = { turnOn: fake.resolves(null), }; From fc4ed224a8127e987be3ca1e603ffb363bb44488 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Wed, 3 May 2023 11:35:48 +0200 Subject: [PATCH 07/20] Tests --- .../test/lib/scene/scene.checkTrigger.test.js | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/server/test/lib/scene/scene.checkTrigger.test.js b/server/test/lib/scene/scene.checkTrigger.test.js index 458586be1e..2872807245 100644 --- a/server/test/lib/scene/scene.checkTrigger.test.js +++ b/server/test/lib/scene/scene.checkTrigger.test.js @@ -17,6 +17,10 @@ describe('scene.checkTrigger', () => { setValue: fake.resolves(null), }; + const deviceFeature = { + device_id: 'light-1', + }; + const brain = {}; beforeEach(() => { @@ -38,6 +42,8 @@ describe('scene.checkTrigger', () => { brain.removeNamedEntity = fake.returns(null); const stateManager = new StateManager(); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); sceneManager = new SceneManager(stateManager, event, device, {}, {}, house, {}, {}, {}, scheduler, brain); }); @@ -54,7 +60,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -91,7 +97,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -128,7 +134,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -164,7 +170,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -200,7 +206,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -236,7 +242,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -271,7 +277,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -306,7 +312,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -343,7 +349,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -380,7 +386,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -417,7 +423,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_OFF, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -454,7 +460,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -491,7 +497,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -530,7 +536,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -569,7 +575,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -615,7 +621,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], @@ -654,7 +660,7 @@ describe('scene.checkTrigger', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], From c95d833ee25fd654e5433e1fb7b3c070d09fc59b Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Wed, 3 May 2023 11:43:47 +0200 Subject: [PATCH 08/20] Tests --- server/test/lib/scene/scene.execute.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/test/lib/scene/scene.execute.test.js b/server/test/lib/scene/scene.execute.test.js index 64fc7455b1..0456a77b81 100644 --- a/server/test/lib/scene/scene.execute.test.js +++ b/server/test/lib/scene/scene.execute.test.js @@ -10,6 +10,9 @@ describe('scene.execute', () => { const event = new EventEmitter(); const brain = {}; const device = {}; + const deviceFeature = { + device_id: 'light-1', + }; let stateManager; let sceneManager; @@ -19,6 +22,8 @@ describe('scene.execute', () => { brain.removeNamedEntity = fake.returns(null); device.setValue = fake.resolves(null); stateManager = new StateManager(event); + stateManager.setState('deviceById', 'light-1', device); + stateManager.setState('deviceFeature', 'light-1-binary', deviceFeature); sceneManager = new SceneManager(stateManager, event, device, {}, {}, {}, {}, {}, {}, {}, brain); }); @@ -34,7 +39,7 @@ describe('scene.execute', () => { [ { type: ACTIONS.LIGHT.TURN_ON, - devices: ['light-1'], + device_features: ['light-1-binary'], }, ], ], From a74193b087c9e320ba74867286d16df6a44139ad Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Fri, 5 May 2023 13:39:16 +0200 Subject: [PATCH 09/20] Comment Pierre-gilles --- .../actions/TurnOnOffLightParams.jsx | 21 ++++-- .../actions/TurnOnOffSwitchParams.jsx | 24 ++++--- server/lib/scene/scene.actions.js | 72 ++++++++++++------- 3 files changed, 75 insertions(+), 42 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 272dd73193..75f9a92804 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx @@ -4,6 +4,8 @@ import { Text } from 'preact-i18n'; import Select from 'react-select'; import { ACTIONS } from '../../../../../../server/utils/constants'; +import { getDeviceFeatureName } from '../../../../utils/device'; +import withIntlAsProp from '../../../../utils/withIntlAsProp'; class TurnOnOffLight extends Component { getOptions = async () => { @@ -12,12 +14,17 @@ class TurnOnOffLight extends Component { device_feature_category: 'light', device_feature_type: 'binary' }); - const deviceFeatureOptions = deviceFeatures - .flatMap(device => device.features) - .map(deviceFeature => ({ - value: deviceFeature.selector, - label: deviceFeature.name - })); + // keep only write lights, not read only + const deviceFeatureOptions = []; + devices.forEach(device => { + device.features + .filter(deviceFeature => deviceFeature.read_only === false) + .map(deviceFeature => ({ + value: deviceFeature.selector, + label: getDeviceFeatureName(this.props.intl.dictionary, device, deviceFeature) + })) + .forEach(deviceFeatureOption => deviceFeatureOptions.push(deviceFeatureOption)); + }); await this.setState({ deviceFeatureOptions }); this.refreshSelectedOptions(this.props); return deviceFeatureOptions; @@ -87,4 +94,4 @@ class TurnOnOffLight extends Component { } } -export default connect('httpClient', {})(TurnOnOffLight); +export default withIntlAsProp(connect('httpClient', {})(TurnOnOffLight)); diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx index 841f2213e3..ffb0d2ce53 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx @@ -4,25 +4,27 @@ import { Text } from 'preact-i18n'; import Select from 'react-select'; import { ACTIONS } from '../../../../../../server/utils/constants'; +import { getDeviceFeatureName } from '../../../../utils/device'; +import withIntlAsProp from '../../../../utils/withIntlAsProp'; class TurnOnOffSwitch extends Component { getOptions = async () => { try { - const deviceFeatures = await this.props.httpClient.get('/api/v1/device', { + const devices = await this.props.httpClient.get('/api/v1/device', { device_feature_category: 'switch', device_feature_type: 'binary' }); // keep only write switches, not read only - const deviceFeaturesFiltered = deviceFeatures.filter(device => { - const writeSwitch = device.features.find(f => f.read_only === false); - return writeSwitch !== undefined; + const deviceFeatureOptions = []; + devices.forEach(device => { + device.features + .filter(deviceFeature => deviceFeature.read_only === false) + .map(deviceFeature => ({ + value: deviceFeature.selector, + label: getDeviceFeatureName(this.props.intl.dictionary, device, deviceFeature) + })) + .forEach(deviceFeatureOption => deviceFeatureOptions.push(deviceFeatureOption)); }); - const deviceFeatureOptions = deviceFeaturesFiltered - .flatMap(device => device.features) - .map(device_feature => ({ - value: device_feature.selector, - label: device_feature.name - })); await this.setState({ deviceFeatureOptions }); this.refreshSelectedOptions(this.props); return deviceFeatureOptions; @@ -92,4 +94,4 @@ class TurnOnOffSwitch extends Component { } } -export default connect('httpClient', {})(TurnOnOffSwitch); +export default withIntlAsProp(connect('httpClient', {})(TurnOnOffSwitch)); diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index e0e7577ebf..1801638769 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -31,55 +31,79 @@ const actionsFunc = { }, [ACTIONS.LIGHT.TURN_ON]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, 1); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, 1); + } + } catch (e) { + logger.warn(e); } }); }, [ACTIONS.LIGHT.TURN_OFF]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, 0); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, 0); + } + } catch (e) { + logger.warn(e); } }); }, [ACTIONS.LIGHT.TOGGLE]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + } + } catch (e) { + logger.warn(e); } }); }, [ACTIONS.SWITCH.TURN_ON]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, 1); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, 1); + } + } catch (e) { + logger.warn(e); } }); }, [ACTIONS.SWITCH.TURN_OFF]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, 0); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, 0); + } + } catch (e) { + logger.warn(e); } }); }, [ACTIONS.SWITCH.TOGGLE]: async (self, action, scope) => { await Promise.map(action.device_features, async (deviceFeatureSelector) => { - const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); - if (deviceFeature) { - const device = self.stateManager.get('deviceById', deviceFeature.device_id); - await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + try { + const deviceFeature = self.stateManager.get('deviceFeature', deviceFeatureSelector); + if (deviceFeature) { + const device = self.stateManager.get('deviceById', deviceFeature.device_id); + await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + } + } catch (e) { + logger.warn(e); } }); }, From 775813c1b1debdd6636920fe7d32885528d2c6ce Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Fri, 5 May 2023 13:52:12 +0200 Subject: [PATCH 10/20] ESLint --- .../routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 75f9a92804..7d444ac7dc 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx @@ -10,7 +10,7 @@ import withIntlAsProp from '../../../../utils/withIntlAsProp'; class TurnOnOffLight extends Component { getOptions = async () => { try { - const deviceFeatures = await this.props.httpClient.get('/api/v1/device', { + const devices = await this.props.httpClient.get('/api/v1/device', { device_feature_category: 'light', device_feature_type: 'binary' }); From cd208ffc063b3c8e7fe6f642ed89d19900f6e0e8 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Fri, 12 May 2023 22:46:44 +0200 Subject: [PATCH 11/20] DB migration --- ...00-update-scene-actions-device-features.js | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 server/migrations/20230505164900-update-scene-actions-device-features.js diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js new file mode 100644 index 0000000000..cab8f310e4 --- /dev/null +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -0,0 +1,78 @@ +const Promise = require('bluebird'); +const db = require('../models'); +const logger = require('../utils/logger'); +const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../utils/constants'); + +module.exports = { + up: async (queryInterface, Sequelize) => { + const scenes = await db.Scene.findAll(); + logger.info(`Scene migration: Found ${scenes.length} scene`); + + await Promise.each(scenes, async (scene) => { + let actionsModified = false; + const updatedActions = await Promise.all( + scene.actions.map(async subactions => { + return Promise.all( + subactions.map(async action => { + if ( + action.type === 'switch.turn-on' || + action.type === 'switch.turn-off' || + action.type === 'switch.toggle' || + action.type === 'light.turn-on' || + action.type === 'light.turn-off' || + action.type === 'light.toggle' + ) { + let category; + let type; + if (action.type.startsWith('switch')) { + category = DEVICE_FEATURE_CATEGORIES.SWITCH; + type = DEVICE_FEATURE_TYPES.SWITCH.BINARY; + } else if (action.type.startsWith('light')) { + category = DEVICE_FEATURE_CATEGORIES.LIGHT; + type = DEVICE_FEATURE_TYPES.LIGHT.BINARY; + } + if (action.devices) { + const devices = await Promise.all(action.devices + .map((deviceSelector) => + db.Device.findOne({ + where: { + selector: deviceSelector, + }, + }), + ) + ); + const deviceIds = devices.map(device => device.id); + const deviceFeatures = await Promise.all(deviceIds + .map(async (deviceId) => + db.DeviceFeature.findOne({ + where: { + device_id: deviceId, + category, + type, + }, + }), + ) + ); + action.device_features = deviceFeatures.map(deviceFeature => deviceFeature.selector); + delete action.devices; + actionsModified = true; + } + } + return action; + }) + ); + }) + ); + + logger.info(`Scene migration: Updating scene ${scene.id} with new actions`); + if (actionsModified) { + scene.set({ + actions: updatedActions, + }); + scene.changed('actions', true); + await scene.save(); + } + }); + }, + down: async (queryInterface, Sequelize) => {}, +}; From d605d028b04cdd1cd7d62043bfd9fa0f76d2aba2 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Fri, 12 May 2023 22:48:30 +0200 Subject: [PATCH 12/20] DB Migration --- ...00-update-scene-actions-device-features.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index cab8f310e4..7366e143ad 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -11,9 +11,9 @@ module.exports = { await Promise.each(scenes, async (scene) => { let actionsModified = false; const updatedActions = await Promise.all( - scene.actions.map(async subactions => { + scene.actions.map(async (subactions) => { return Promise.all( - subactions.map(async action => { + subactions.map(async (action) => { if ( action.type === 'switch.turn-on' || action.type === 'switch.turn-off' || @@ -32,18 +32,18 @@ module.exports = { type = DEVICE_FEATURE_TYPES.LIGHT.BINARY; } if (action.devices) { - const devices = await Promise.all(action.devices - .map((deviceSelector) => + const devices = await Promise.all( + action.devices.map((deviceSelector) => db.Device.findOne({ where: { selector: deviceSelector, }, }), - ) + ), ); - const deviceIds = devices.map(device => device.id); - const deviceFeatures = await Promise.all(deviceIds - .map(async (deviceId) => + const deviceIds = devices.map((device) => device.id); + const deviceFeatures = await Promise.all( + deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ where: { device_id: deviceId, @@ -51,17 +51,17 @@ module.exports = { type, }, }), - ) + ), ); - action.device_features = deviceFeatures.map(deviceFeature => deviceFeature.selector); + action.device_features = deviceFeatures.map((deviceFeature) => deviceFeature.selector); delete action.devices; actionsModified = true; } } return action; - }) + }), ); - }) + }), ); logger.info(`Scene migration: Updating scene ${scene.id} with new actions`); From 24b4b8ef40d0638c62052ad8d02fecfe154de41f Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Sat, 13 May 2023 00:13:02 +0200 Subject: [PATCH 13/20] Add check --- .../20230505164900-update-scene-actions-device-features.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index 7366e143ad..efa9f52d95 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -41,7 +41,9 @@ module.exports = { }), ), ); - const deviceIds = devices.map((device) => device.id); + const deviceIds = devices + .filter(device => device !== undefined) + .map((device) => device.id); const deviceFeatures = await Promise.all( deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ From 5d79ae4a9cb3f8a64a25a2d5d7fbd6ed51a1c699 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Sat, 13 May 2023 10:47:53 +0200 Subject: [PATCH 14/20] DB Migration --- .../20230505164900-update-scene-actions-device-features.js | 7 ++++--- server/test/lib/scene/scene.executeActions.test.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index efa9f52d95..ed27affc91 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -41,9 +41,10 @@ module.exports = { }), ), ); - const deviceIds = devices - .filter(device => device !== undefined) - .map((device) => device.id); + + devices.forEach((device) => console.log(device)); + + const deviceIds = devices.filter((device) => device !== undefined).map((device) => device.id); const deviceFeatures = await Promise.all( deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ diff --git a/server/test/lib/scene/scene.executeActions.test.js b/server/test/lib/scene/scene.executeActions.test.js index b3167b7695..2704263dc4 100644 --- a/server/test/lib/scene/scene.executeActions.test.js +++ b/server/test/lib/scene/scene.executeActions.test.js @@ -211,7 +211,7 @@ describe('scene.executeActions', () => { }, }; stateManager.setState('deviceById', 'switch-1', device); - stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature) + stateManager.setState('deviceFeature', 'switch-1-binary', deviceFeature); await executeActions( { stateManager, event, device }, [ From 3ec5cf68d8a2d91d4a7626d3002f555407e153cb Mon Sep 17 00:00:00 2001 From: Pochet Romuald Date: Sat, 13 May 2023 11:04:18 +0200 Subject: [PATCH 15/20] Update 20230505164900-update-scene-actions-device-features.js --- .../20230505164900-update-scene-actions-device-features.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index ed27affc91..7188a24181 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -42,9 +42,7 @@ module.exports = { ), ); - devices.forEach((device) => console.log(device)); - - const deviceIds = devices.filter((device) => device !== undefined).map((device) => device.id); + const deviceIds = devices.filter((device) => device).map((device) => device.id); const deviceFeatures = await Promise.all( deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ From 6e5ec2bc8a3d012cc68276d3c737ac1faf695bf9 Mon Sep 17 00:00:00 2001 From: Pochet Romuald Date: Sat, 13 May 2023 11:05:05 +0200 Subject: [PATCH 16/20] Update 20230505164900-update-scene-actions-device-features.js --- ...00-update-scene-actions-device-features.js | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index 7188a24181..c82f7aadb3 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -43,20 +43,22 @@ module.exports = { ); const deviceIds = devices.filter((device) => device).map((device) => device.id); - const deviceFeatures = await Promise.all( - deviceIds.map(async (deviceId) => - db.DeviceFeature.findOne({ - where: { - device_id: deviceId, - category, - type, - }, - }), - ), - ); - action.device_features = deviceFeatures.map((deviceFeature) => deviceFeature.selector); - delete action.devices; - actionsModified = true; + if(deviceIds && deviceIds.length > 0) { + const deviceFeatures = await Promise.all( + deviceIds.map(async (deviceId) => + db.DeviceFeature.findOne({ + where: { + device_id: deviceId, + category, + type, + }, + }), + ), + ); + action.device_features = deviceFeatures.map((deviceFeature) => deviceFeature.selector); + delete action.devices; + actionsModified = true; + } } } return action; From e84a707392f8edd14e0c20178eb1c49e066e1549 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Sat, 13 May 2023 11:20:53 +0200 Subject: [PATCH 17/20] DB Migration --- .../20230505164900-update-scene-actions-device-features.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index ed27affc91..068300bea7 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -6,7 +6,7 @@ const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../utils/co module.exports = { up: async (queryInterface, Sequelize) => { const scenes = await db.Scene.findAll(); - logger.info(`Scene migration: Found ${scenes.length} scene`); + logger.info(`Scene migration: Found ${scenes.length} scene(s)`); await Promise.each(scenes, async (scene) => { let actionsModified = false; @@ -42,9 +42,7 @@ module.exports = { ), ); - devices.forEach((device) => console.log(device)); - - const deviceIds = devices.filter((device) => device !== undefined).map((device) => device.id); + const deviceIds = devices.filter((device) => device).map((device) => device.id); const deviceFeatures = await Promise.all( deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ From 00435474f2aa3feda888c6635d244a4db5354511 Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Sat, 13 May 2023 11:27:47 +0200 Subject: [PATCH 18/20] DB Migration --- .../20230505164900-update-scene-actions-device-features.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230505164900-update-scene-actions-device-features.js index 7fcbf318fd..37bbdbfa4e 100644 --- a/server/migrations/20230505164900-update-scene-actions-device-features.js +++ b/server/migrations/20230505164900-update-scene-actions-device-features.js @@ -43,7 +43,7 @@ module.exports = { ); const deviceIds = devices.filter((device) => device).map((device) => device.id); - if(deviceIds && deviceIds.length > 0) { + if (deviceIds && deviceIds.length > 0) { const deviceFeatures = await Promise.all( deviceIds.map(async (deviceId) => db.DeviceFeature.findOne({ From f980c7480cda246fdd02b1d451b0de26647d197a Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Wed, 16 Aug 2023 11:59:57 +0200 Subject: [PATCH 19/20] Update DB migration scriptdate --- ....js => 20230816115900-update-scene-actions-device-features.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename server/migrations/{20230505164900-update-scene-actions-device-features.js => 20230816115900-update-scene-actions-device-features.js} (100%) diff --git a/server/migrations/20230505164900-update-scene-actions-device-features.js b/server/migrations/20230816115900-update-scene-actions-device-features.js similarity index 100% rename from server/migrations/20230505164900-update-scene-actions-device-features.js rename to server/migrations/20230816115900-update-scene-actions-device-features.js From 9e88a529208e07154fcf7c7c557c08a8504fd19f Mon Sep 17 00:00:00 2001 From: POCHET Romuald Date: Thu, 17 Aug 2023 21:18:51 +0200 Subject: [PATCH 20/20] Migration test --- ...date_scene_actions_device_features.test.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 server/test/lib/device/device.update_scene_actions_device_features.test.js diff --git a/server/test/lib/device/device.update_scene_actions_device_features.test.js b/server/test/lib/device/device.update_scene_actions_device_features.test.js new file mode 100644 index 0000000000..a1169d29d5 --- /dev/null +++ b/server/test/lib/device/device.update_scene_actions_device_features.test.js @@ -0,0 +1,41 @@ +const { expect } = require('chai'); +const { fake } = require('sinon'); +const { up } = require('../../../migrations/20230816115900-update-scene-actions-device-features'); +const StateManager = require('../../../lib/state'); +const SceneManager = require('../../../lib/scene'); +const Event = require('../../../lib/event'); + +describe('Device', () => { + it('should create device alone', async () => { + const event = new Event(); + const stateManager = new StateManager(event); + const sceneManager = new SceneManager( + stateManager, + event, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + { + addNamedEntity: fake.returns(null), + }, + ); + + const scene = await sceneManager.create({ + id: 'device.update_scene_actions_device_features.test', + selector: 'device_update_scene_actions_device_features_test', + name: 'device.update_scene_actions_device_features.test', + icon: 'fe-scene', + triggers: [], + actions: [], + }); + + up(null, null); + + expect(scene).to.have.property('name', 'Philips Hue 1'); + }); +});