diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 51a97d818f..7d444ac7dc 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,13 +14,20 @@ class TurnOnOffLight extends Component { device_feature_category: 'light', device_feature_type: 'binary' }); - const deviceOptions = devices.map(device => ({ - value: device.selector, - label: device.name - })); - await this.setState({ deviceOptions }); + // 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 deviceOptions; + return deviceFeatureOptions; } catch (e) { console.error(e); } @@ -26,18 +35,20 @@ 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 +57,7 @@ class TurnOnOffLight extends Component { constructor(props) { super(props); this.state = { - deviceOptions: null, + deviceFeatureOptions: null, selectedOptions: [] }; } @@ -58,7 +69,7 @@ class TurnOnOffLight extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions }) { + render(props, { selectedOptions, deviceFeatureOptions }) { return (
); } } -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 c485b41df1..ffb0d2ce53 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffSwitchParams.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 TurnOnOffSwitch extends Component { getOptions = async () => { @@ -13,17 +15,19 @@ class TurnOnOffSwitch extends Component { device_feature_type: 'binary' }); // keep only write switches, not read only - const devicesFiltered = devices.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 deviceOptions = devicesFiltered.map(device => ({ - value: device.selector, - label: device.name - })); - await this.setState({ deviceOptions }); + await this.setState({ deviceFeatureOptions }); this.refreshSelectedOptions(this.props); - return deviceOptions; + return deviceFeatureOptions; } catch (e) { console.error(e); } @@ -31,18 +35,20 @@ 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 +57,7 @@ class TurnOnOffSwitch extends Component { constructor(props) { super(props); this.state = { - deviceOptions: null, + deviceFeatureOptions: null, selectedOptions: [] }; } @@ -63,7 +69,7 @@ class TurnOnOffSwitch extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions }) { + render(props, { selectedOptions, deviceFeatureOptions }) { return (
); } } -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 a7ade930f4..7825b161a9 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -18,7 +18,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'); @@ -64,90 +64,78 @@ const actionsFunc = { return self.device.setValue(device, deviceFeature, value); }, [ACTIONS.LIGHT.TURN_ON]: 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, - ); - await self.device.setValue(device, deviceFeature, 1); + 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.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, - ); - await self.device.setValue(device, deviceFeature, 0); + 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.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, - ); - await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + 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.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, - ); - await self.device.setValue(device, deviceFeature, 1); + 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.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, - ); - await self.device.setValue(device, deviceFeature, 0); + 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.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, - ); - await self.device.setValue(device, deviceFeature, deviceFeature.last_value === 0 ? 1 : 0); + 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/migrations/20230816115900-update-scene-actions-device-features.js b/server/migrations/20230816115900-update-scene-actions-device-features.js new file mode 100644 index 0000000000..37bbdbfa4e --- /dev/null +++ b/server/migrations/20230816115900-update-scene-actions-device-features.js @@ -0,0 +1,81 @@ +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(s)`); + + 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.filter((device) => device).map((device) => device.id); + 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; + }), + ); + }), + ); + + 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) => {}, +}; 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'); + }); +}); 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'], }, ], ], 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'], }, ], ], diff --git a/server/test/lib/scene/scene.executeActions.test.js b/server/test/lib/scene/scene.executeActions.test.js index 79c8261815..2704263dc4 100644 --- a/server/test/lib/scene/scene.executeActions.test.js +++ b/server/test/lib/scene/scene.executeActions.test.js @@ -24,24 +24,24 @@ 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), }, }; - - 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'], }, ], ], @@ -53,24 +53,24 @@ 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), }, }; - - 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'], }, ], ], @@ -82,6 +82,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 = { @@ -89,18 +90,17 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; - - 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'], }, ], ], @@ -112,6 +112,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 = { @@ -119,18 +120,17 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.LIGHT, type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - find: fake.returns(deviceFeature), }, }; - - 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'], }, ], ], @@ -138,53 +138,28 @@ 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'), - }, - }; - - 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), }, }; - - 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'], }, ], ], @@ -196,24 +171,24 @@ 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), }, }; - - 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'], }, ], ], @@ -225,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 = { @@ -232,18 +208,17 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; - - 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'], }, ], ], @@ -255,6 +230,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 = { @@ -262,18 +238,17 @@ describe('scene.executeActions', () => { features: { category: DEVICE_FEATURE_CATEGORIES.SWITCH, type: DEVICE_FEATURE_TYPES.SWITCH.BINARY, - find: fake.returns(deviceFeature), }, }; - - 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'], }, ], ], @@ -364,32 +339,40 @@ describe('scene.executeActions', () => { const device = { setValue: fake.resolves(null), }; - + const deviceFeatureLight = { + device_id: 'device', + }; + const deviceFeatureSwitch = { + device_id: 'device', + }; + 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'], }, ], ], 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); });