From d69197d7ed218bf0332872b3e655e5a1b47ace57 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Thu, 18 Jan 2024 22:08:18 +0100 Subject: [PATCH 01/18] feat(action): Add blinking lights --- front/src/config/i18n/de.json | 13 +- front/src/config/i18n/en.json | 13 +- front/src/config/i18n/fr.json | 15 ++- .../routes/scene/edit-scene/ActionCard.jsx | 13 +- .../edit-scene/actions/BlinkLightParams.jsx | 113 ++++++++++++++++++ .../actions/ChooseActionTypeCard.jsx | 1 + server/lib/scene/scene.actions.js | 20 ++++ server/models/scene.js | 2 + server/utils/constants.js | 1 + 9 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx diff --git a/front/src/config/i18n/de.json b/front/src/config/i18n/de.json index f4ff49b0e5..79af77d0a8 100644 --- a/front/src/config/i18n/de.json +++ b/front/src/config/i18n/de.json @@ -1675,6 +1675,16 @@ "label": "Wähle die Lichter aus, die umgeschaltet werden sollen", "description": "Diese Aktion schaltet das Licht ein, wenn es ausgeschaltet ist bzw. schaltet es aus, wenn es eingeschaltet ist" }, + "blinkLights": { + "label": "Wählen Sie die Lichter aus, die Sie zum Blinken bringen möchten", + "description": "Konfigurieren Sie die Verzögerung zwischen jedem Blinken und die Anzahl der Blinksignale", + "timesToBlink": { + "placeholder": "3" + }, + "waitingTime": { + "placeholder": "200" + } + }, "turnOnSwitches": { "label": "Wähle die Schalter aus, die eingeschaltet werden sollen" }, @@ -1816,7 +1826,8 @@ "light": { "turn-on": "Licht einschalten", "turn-off": "Licht ausschalten", - "toggle": "Lichter umschalten" + "toggle": "Lichter umschalten", + "blink": "Lichter blinken lassen" }, "switch": { "turn-on": "Schalter einschalten", diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 1b832441d4..047621b039 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -1677,6 +1677,16 @@ "label": "Select the lights you want to toggle", "description": "This action turn on the light if state is off or turn off the light if state is on" }, + "blinkLights": { + "label": "Select the lights you want to make blink", + "description": "Configure delay between each blink and number of blinks", + "timesToBlink": { + "placeholder": "3" + }, + "waitingTime": { + "placeholder": "200" + } + }, "turnOnSwitches": { "label": "Select the switches you want to turn on" }, @@ -1818,7 +1828,8 @@ "light": { "turn-on": "Turn On the Lights", "turn-off": "Turn Off the Lights", - "toggle": "Toggle the Lights" + "toggle": "Toggle the Lights", + "blink": "Make the Lights blink" }, "switch": { "turn-on": "Turn On the Switches", diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json index 1bcbaad197..6a0baad720 100644 --- a/front/src/config/i18n/fr.json +++ b/front/src/config/i18n/fr.json @@ -1679,6 +1679,16 @@ "label": "Sélectionnez les lumières que vous souhaitez inverser", "description": "Cette action allume la lumière si éteinte, sinon éteins la lumière" }, + "blinkLights": { + "label": "Sélectionnez les lumières que vous souhaitez faire clignoter", + "description": "Réglez le nombre de fois et le temps entre chaque clignotement", + "timesToBlink": { + "placeholder": "3 fois" + }, + "waitingTime": { + "placeholder": "200ms" + } + }, "turnOnSwitches": { "label": "Sélectionnez les prises que vous souhaitez allumer" }, @@ -1818,9 +1828,10 @@ }, "delay": "Attendre", "light": { - "turn-on": "Allumer la lumière", + "turn-on": "Allumer les lumières", "turn-off": "Eteindre les lumières", - "toggle": "Inverser les lumières" + "toggle": "Inverser les lumières", + "blink": "Faire clignoter les lumières" }, "switch": { "turn-on": "Allumer les prises", diff --git a/front/src/routes/scene/edit-scene/ActionCard.jsx b/front/src/routes/scene/edit-scene/ActionCard.jsx index e8d9ca58e7..5cf5bd784f 100644 --- a/front/src/routes/scene/edit-scene/ActionCard.jsx +++ b/front/src/routes/scene/edit-scene/ActionCard.jsx @@ -15,6 +15,7 @@ import DeviceSetValue from './actions/DeviceSetValue'; import SendMessageParams from './actions/SendMessageParams'; import OnlyContinueIfParams from './actions/only-continue-if/OnlyContinueIfParams'; import TurnOnOffLightParams from './actions/TurnOnOffLightParams'; +import BlinkLightParams from './actions/BlinkLightParams'; import TurnOnOffSwitchParams from './actions/TurnOnOffSwitchParams'; import StartSceneParams from './actions/StartSceneParams'; import UserPresence from './actions/UserPresence'; @@ -38,6 +39,7 @@ const ACTION_ICON = { [ACTIONS.LIGHT.TURN_ON]: 'fe fe-toggle-right', [ACTIONS.LIGHT.TURN_OFF]: 'fe fe-toggle-left', [ACTIONS.LIGHT.TOGGLE]: 'fe fe-shuffle', + [ACTIONS.LIGHT.BLINK]: 'fe fe-star', [ACTIONS.SWITCH.TURN_ON]: 'fe fe-toggle-right', [ACTIONS.SWITCH.TURN_OFF]: 'fe fe-toggle-left', [ACTIONS.SWITCH.TOGGLE]: 'fe fe-shuffle', @@ -97,7 +99,8 @@ const ActionCard = ({ children, ...props }) => { 'col-lg-6': props.action.type === ACTIONS.MESSAGE.SEND || props.action.type === ACTIONS.CALENDAR.IS_EVENT_RUNNING || - props.action.type === ACTIONS.MQTT.SEND, + props.action.type === ACTIONS.MQTT.SEND || + props.action.type === ACTIONS.LIGHT.BLINK, 'col-lg-4': props.action.type !== ACTIONS.CONDITION.ONLY_CONTINUE_IF && props.action.type !== ACTIONS.MESSAGE.SEND && @@ -173,6 +176,14 @@ const ActionCard = ({ children, ...props }) => { updateActionProperty={props.updateActionProperty} /> )} + {props.action.type === ACTIONS.LIGHT.BLINK && ( + + )} {props.action.type === ACTIONS.SWITCH.TURN_ON && ( { + try { + const devices = 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 }); + this.refreshSelectedOptions(this.props); + return deviceOptions; + } catch (e) { + console.error(e); + } + }; + handleChange = selectedOptions => { + if (selectedOptions) { + const lights = selectedOptions.map(selectedOption => selectedOption.value); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', lights); + } else { + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', []); + } + }; + handleChangeTimesToBlink = e => { + let newValue = Number.isInteger(parseInt(e.target.value, 10)) ? parseInt(e.target.value, 10) : 0; + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'timesToBlink', newValue); + }; + handleChangeWaitingTime = e => { + let newValue = Number.isInteger(parseInt(e.target.value, 10)) ? parseInt(e.target.value, 10) : 0; + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'waitingTime', newValue); + }; + 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); + } + }); + } + this.setState({ selectedOptions }); + }; + constructor(props) { + super(props); + this.state = { + deviceOptions: null, + selectedOptions: [] + }; + } + async componentDidMount() { + this.getOptions(); + } + + componentWillReceiveProps(nextProps) { + this.refreshSelectedOptions(nextProps); + } + + render(props, { selectedOptions, deviceOptions }) { + return ( +
+
+ + } + /> + +
+
+ toutes les + + } + /> + +
+
+ + ); + } +} + +export default connect('httpClient', {})(BlinkLight); diff --git a/front/src/routes/scene/edit-scene/actions/ChooseActionTypeCard.jsx b/front/src/routes/scene/edit-scene/actions/ChooseActionTypeCard.jsx index 17e8f5ed6e..61d315c8ef 100644 --- a/front/src/routes/scene/edit-scene/actions/ChooseActionTypeCard.jsx +++ b/front/src/routes/scene/edit-scene/actions/ChooseActionTypeCard.jsx @@ -9,6 +9,7 @@ const ACTION_LIST = [ ACTIONS.LIGHT.TURN_ON, ACTIONS.LIGHT.TURN_OFF, ACTIONS.LIGHT.TOGGLE, + ACTIONS.LIGHT.BLINK, ACTIONS.SWITCH.TURN_ON, ACTIONS.SWITCH.TURN_OFF, ACTIONS.SWITCH.TOGGLE, diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 9055c8f9a9..43d9ac8ed6 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -110,6 +110,26 @@ const actionsFunc = { } }); }, + [ACTIONS.LIGHT.BLINK]: async (self, action, scope) => { + await Promise.map(action.devices, async (deviceSelector) => { + try { + const device = self.stateManager.get('device', deviceSelector); + const deviceFeature = getDeviceFeature( + device, + DEVICE_FEATURE_CATEGORIES.LIGHT, + DEVICE_FEATURE_TYPES.LIGHT.BINARY, + ); + const [timesToBlink, waitingTime ] =action; + const timerId = setInterval(() => { + self.device.setValue(device, deviceFeature, 1); + self.device.setValue(device, deviceFeature, 0); + }, waitingTime); + setTimeout(() => clearInterval(timerId), waitingTime * 2 * timesToBlink); + } catch (e) { + logger.warn(e); + } + }); + }, [ACTIONS.SWITCH.TURN_ON]: async (self, action, scope) => { await Promise.map(action.devices, async (deviceSelector) => { try { diff --git a/server/models/scene.js b/server/models/scene.js index 49ea35f7fe..3a60fbfbf3 100644 --- a/server/models/scene.js +++ b/server/models/scene.js @@ -60,6 +60,8 @@ const actionSchema = Joi.array().items( alarm_mode: Joi.string().valid(...ALARM_MODES_LIST), topic: Joi.string(), message: Joi.string().allow(''), + timesToBlink: Joi.number(), + waitingTime: Joi.number(), }), ), ); diff --git a/server/utils/constants.js b/server/utils/constants.js index 38fbe4bcef..e4805cb233 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -335,6 +335,7 @@ const ACTIONS = { TURN_ON: 'light.turn-on', TURN_OFF: 'light.turn-off', TOGGLE: 'light.toggle', + BLINK: 'light.blink', }, SWITCH: { TURN_ON: 'switch.turn-on', From 328884ef217677fe28f3106ded4e3ab4890936f8 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Thu, 18 Jan 2024 22:44:49 +0100 Subject: [PATCH 02/18] feat(action): Blink lights frontend style --- .../edit-scene/actions/BlinkLightParams.jsx | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index c5dcb4d52e..8f74ff1925 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -66,22 +66,24 @@ class BlinkLight extends Component { render(props, { selectedOptions, deviceOptions }) { return ( -
-
- - +
-
-
- Répéter +
+
+
Répéter
-
- toutes les +
+
+
+
toutes les
- } - /> +
+ } + /> +
+ + ms + +
+
From db48828fd4772afee73b76427587314f61525865 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Fri, 19 Jan 2024 14:49:49 +0100 Subject: [PATCH 03/18] fix(action): Fix blinking lights --- server/lib/scene/scene.actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 43d9ac8ed6..747d1393ff 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -119,12 +119,12 @@ const actionsFunc = { DEVICE_FEATURE_CATEGORIES.LIGHT, DEVICE_FEATURE_TYPES.LIGHT.BINARY, ); - const [timesToBlink, waitingTime ] =action; + const { timesToBlink, waitingTime } = action; const timerId = setInterval(() => { self.device.setValue(device, deviceFeature, 1); self.device.setValue(device, deviceFeature, 0); }, waitingTime); - setTimeout(() => clearInterval(timerId), waitingTime * 2 * timesToBlink); + setTimeout(() => clearInterval(timerId), waitingTime * (timesToBlink + 1)); } catch (e) { logger.warn(e); } From 086e58fe3f69e43d1fedb4348195446f4f2656e5 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Wed, 24 Jan 2024 23:32:54 +0100 Subject: [PATCH 04/18] fix: Improve frontend and usability --- front/src/config/i18n/de.json | 12 ++- front/src/config/i18n/en.json | 12 ++- front/src/config/i18n/fr.json | 14 ++- .../edit-scene/actions/BlinkLightParams.jsx | 96 ++++++++++--------- server/lib/scene/scene.actions.js | 26 ++++- server/models/scene.js | 4 +- 6 files changed, 97 insertions(+), 67 deletions(-) diff --git a/front/src/config/i18n/de.json b/front/src/config/i18n/de.json index 79af77d0a8..9f3b849684 100644 --- a/front/src/config/i18n/de.json +++ b/front/src/config/i18n/de.json @@ -1677,12 +1677,16 @@ }, "blinkLights": { "label": "Wählen Sie die Lichter aus, die Sie zum Blinken bringen möchten", - "description": "Konfigurieren Sie die Verzögerung zwischen jedem Blinken und die Anzahl der Blinksignale", - "timesToBlink": { + "description": "Stellen Sie die Dauer und die Geschwindigkeit des Blinkens ein", + "blinkingTime": { + "label": "Dauer (in Sekunden)", "placeholder": "3" }, - "waitingTime": { - "placeholder": "200" + "blinkingSpeed": { + "label": "Modus", + "slow": "Langsam", + "medium": "Mittel", + "fast": "Schnell" } }, "turnOnSwitches": { diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 047621b039..e59987fc1e 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -1679,12 +1679,16 @@ }, "blinkLights": { "label": "Select the lights you want to make blink", - "description": "Configure delay between each blink and number of blinks", - "timesToBlink": { + "description": "Configure blinking duration and speed", + "blinkingTime": { + "label": "Duration (in seconds)", "placeholder": "3" }, - "waitingTime": { - "placeholder": "200" + "blinkingSpeed": { + "label": "Mode", + "slow": "Slow", + "medium": "Medium", + "fast": "Fast" } }, "turnOnSwitches": { diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json index 6a0baad720..bab4c656bd 100644 --- a/front/src/config/i18n/fr.json +++ b/front/src/config/i18n/fr.json @@ -1681,12 +1681,16 @@ }, "blinkLights": { "label": "Sélectionnez les lumières que vous souhaitez faire clignoter", - "description": "Réglez le nombre de fois et le temps entre chaque clignotement", - "timesToBlink": { - "placeholder": "3 fois" + "description": "Réglez la durée et la vitesse de clignotement", + "blinkingTime": { + "label": "Durée (en secondes)", + "placeholder": "3" }, - "waitingTime": { - "placeholder": "200ms" + "blinkingSpeed": { + "label": "Mode", + "slow": "Lent", + "medium": "Normal", + "fast": "Rapide" } }, "turnOnSwitches": { diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index 8f74ff1925..7454206b14 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -29,13 +29,13 @@ class BlinkLight extends Component { this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'devices', []); } }; - handleChangeTimesToBlink = e => { + handleChangeBlinkingTime = e => { let newValue = Number.isInteger(parseInt(e.target.value, 10)) ? parseInt(e.target.value, 10) : 0; - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'timesToBlink', newValue); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingTime', newValue); }; - handleChangeWaitingTime = e => { - let newValue = Number.isInteger(parseInt(e.target.value, 10)) ? parseInt(e.target.value, 10) : 0; - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'waitingTime', newValue); + handleChangeBlinkingSpeed = e => { + console.log(e.target.value); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingSpeed', e.target.value); }; refreshSelectedOptions = nextProps => { const selectedOptions = []; @@ -64,57 +64,59 @@ class BlinkLight extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions }) { + render(props, { selectedOptions, deviceOptions, blinkingSpeed }) { return ( -
-
-
-
- -
- } +
+
+
+
+
+ +
+ } + value={props.action.blinkingTime} + onChange={this.handleChangeBlinkingTime} + placeholder={} /> -
- - ms - -
+ +
+
+
+
+
+
- + +
diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 747d1393ff..13411f55b2 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -111,6 +111,22 @@ const actionsFunc = { }); }, [ACTIONS.LIGHT.BLINK]: async (self, action, scope) => { + const { blinkingSpeed, blinkingTime } = action; + let blinkingInterval; + switch (blinkingSpeed) { + case 'slow': + blinkingInterval = 1000; + break; + case 'medium': + blinkingInterval = 500; + break; + case 'fast': + blinkingInterval = 100; + break; + default: + blinkingInterval = 100; + break; + } await Promise.map(action.devices, async (deviceSelector) => { try { const device = self.stateManager.get('device', deviceSelector); @@ -119,12 +135,12 @@ const actionsFunc = { DEVICE_FEATURE_CATEGORIES.LIGHT, DEVICE_FEATURE_TYPES.LIGHT.BINARY, ); - const { timesToBlink, waitingTime } = action; const timerId = setInterval(() => { - self.device.setValue(device, deviceFeature, 1); - self.device.setValue(device, deviceFeature, 0); - }, waitingTime); - setTimeout(() => clearInterval(timerId), waitingTime * (timesToBlink + 1)); + const newValue = deviceFeature.last_value === 0 ? 1 : 0; + self.device.setValue(device, deviceFeature, newValue); + self.device.setValue(device, deviceFeature, !newValue); + }, blinkingInterval); + setTimeout(() => clearInterval(timerId), blinkingTime); } catch (e) { logger.warn(e); } diff --git a/server/models/scene.js b/server/models/scene.js index 3a60fbfbf3..9eee2a5336 100644 --- a/server/models/scene.js +++ b/server/models/scene.js @@ -60,8 +60,8 @@ const actionSchema = Joi.array().items( alarm_mode: Joi.string().valid(...ALARM_MODES_LIST), topic: Joi.string(), message: Joi.string().allow(''), - timesToBlink: Joi.number(), - waitingTime: Joi.number(), + blinkingTime: Joi.number(), + blinkingSpeed: Joi.string().valid('slow', 'medium', 'fast'), }), ), ); From 040cfe3ff0c1f74eb764f754fc7e8fbdf4c967c1 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Thu, 25 Jan 2024 11:56:00 +0100 Subject: [PATCH 05/18] fix: Fix backend action --- server/lib/scene/scene.actions.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 13411f55b2..99239fb37b 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -135,12 +135,15 @@ const actionsFunc = { DEVICE_FEATURE_CATEGORIES.LIGHT, DEVICE_FEATURE_TYPES.LIGHT.BINARY, ); + const oldValue = deviceFeature.last_value; const timerId = setInterval(() => { - const newValue = deviceFeature.last_value === 0 ? 1 : 0; - self.device.setValue(device, deviceFeature, newValue); - self.device.setValue(device, deviceFeature, !newValue); + self.device.setValue(device, deviceFeature, 1); + self.device.setValue(device, deviceFeature, 0); }, blinkingInterval); - setTimeout(() => clearInterval(timerId), blinkingTime); + setTimeout(() => { + clearInterval(timerId); + self.device.setValue(device, deviceFeature, oldValue); + }, blinkingTime * 1000); } catch (e) { logger.warn(e); } From ddcda457a14ef5fbe8e787daa87d8116c1cfc821 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Thu, 25 Jan 2024 23:11:07 +0100 Subject: [PATCH 06/18] Add tests --- server/lib/scene/scene.actions.js | 6 +- .../actions/scene.action.blinkLights.test.js | 249 ++++++++++++++++++ 2 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 server/test/lib/scene/actions/scene.action.blinkLights.test.js diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 99239fb37b..655a547b4f 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -141,9 +141,9 @@ const actionsFunc = { self.device.setValue(device, deviceFeature, 0); }, blinkingInterval); setTimeout(() => { - clearInterval(timerId); - self.device.setValue(device, deviceFeature, oldValue); - }, blinkingTime * 1000); + clearInterval(timerId); + self.device.setValue(device, deviceFeature, oldValue); + }, blinkingTime * 1000); } catch (e) { logger.warn(e); } diff --git a/server/test/lib/scene/actions/scene.action.blinkLights.test.js b/server/test/lib/scene/actions/scene.action.blinkLights.test.js new file mode 100644 index 0000000000..4e938f9d54 --- /dev/null +++ b/server/test/lib/scene/actions/scene.action.blinkLights.test.js @@ -0,0 +1,249 @@ +const { fake, assert, useFakeTimers } = require('sinon'); +const EventEmitter = require('events'); + +const { ACTIONS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants'); +const { executeActions } = require('../../../../lib/scene/scene.executeActions'); + +const StateManager = require('../../../../lib/state'); + +const event = new EventEmitter(); + +describe('scene.blink-lights', () => { + let clock; + + beforeEach(() => { + clock = useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should blink light in slow mode', async () => { + const stateManager = new StateManager(event); + const deviceFeature = { + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + last_value: 0, + }; + 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); + + const scope = {}; + await executeActions( + { stateManager, event, device }, + [ + [ + { + type: ACTIONS.LIGHT.BLINK, + devices: ['light-1'], + blinkingSpeed: 'slow', + blinkingTime: 2, + }, + ], + ], + scope, + ); + clock.tick(2000); + assert.calledWithExactly(device.setValue, device, deviceFeature, 0); + assert.calledWithExactly(device.setValue, device, deviceFeature, 1); + assert.callCount(device.setValue, 5); + }); + + it('should blink light in slow mode', async () => { + const stateManager = new StateManager(event); + const deviceFeature = { + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + last_value: 0, + }; + 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); + + const scope = {}; + await executeActions( + { stateManager, event, device }, + [ + [ + { + type: ACTIONS.LIGHT.BLINK, + devices: ['light-1'], + blinkingSpeed: 'slow', + blinkingTime: 2, + }, + ], + ], + scope, + ); + clock.tick(2000); + assert.calledWithExactly(device.setValue, device, deviceFeature, 0); + assert.calledWithExactly(device.setValue, device, deviceFeature, 1); + // 2 seconds * 1 blink / sec * on/off + 1 restore + assert.callCount(device.setValue, 5); + }); + + it('should blink light in medium mode', async () => { + const stateManager = new StateManager(event); + const deviceFeature = { + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + last_value: 0, + }; + 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); + + const scope = {}; + await executeActions( + { stateManager, event, device }, + [ + [ + { + type: ACTIONS.LIGHT.BLINK, + devices: ['light-1'], + blinkingSpeed: 'medium', + blinkingTime: 2, + }, + ], + ], + scope, + ); + clock.tick(2000); + assert.calledWithExactly(device.setValue, device, deviceFeature, 0); + assert.calledWithExactly(device.setValue, device, deviceFeature, 1); + // 2 seconds * 2 blinks / sec * on/off + 1 restore + assert.callCount(device.setValue, 9); + }); + + it('should blink light in fast mode', async () => { + const stateManager = new StateManager(event); + const deviceFeature = { + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + last_value: 0, + }; + 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); + + const scope = {}; + await executeActions( + { stateManager, event, device }, + [ + [ + { + type: ACTIONS.LIGHT.BLINK, + devices: ['light-1'], + blinkingSpeed: 'fast', + blinkingTime: 2, + }, + ], + ], + scope, + ); + clock.tick(2000); + assert.calledWithExactly(device.setValue, device, deviceFeature, 0); + assert.calledWithExactly(device.setValue, device, deviceFeature, 1); + // 2 seconds * 10 blinks / sec * on/off + 1 restore + assert.callCount(device.setValue, 41); + }); + + it('should blink light in unknown mode', async () => { + const stateManager = new StateManager(event); + const deviceFeature = { + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + last_value: 0, + }; + 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); + + const scope = {}; + await executeActions( + { stateManager, event, device }, + [ + [ + { + type: ACTIONS.LIGHT.BLINK, + devices: ['light-1'], + blinkingSpeed: 'unknown', + blinkingTime: 2, + }, + ], + ], + scope, + ); + clock.tick(2000); + assert.calledWithExactly(device.setValue, device, deviceFeature, 0); + assert.calledWithExactly(device.setValue, device, deviceFeature, 1); + // 2 seconds * 10 blinks / sec * on/off + 1 restore + assert.callCount(device.setValue, 41); + }); + + it('should throw error when blinking light', async () => { + const stateManager = new StateManager(event); + const device = { + setValue: fake.throws('An error occured'), + 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.BLINK, + devices: ['my-device'], + blinkingTime: 1, + blinkingSpeed: 'slow', + }, + ], + ], + {}, + ); + assert.notCalled(device.setValue); + }); +}); From dacb010bb8bb6ee2d0f42733932c8c498292377a Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Sat, 27 Jan 2024 20:58:12 +0100 Subject: [PATCH 07/18] From flash to blink --- server/lib/scene/scene.actions.js | 10 ++-- .../actions/scene.action.blinkLights.test.js | 48 ++----------------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 655a547b4f..39dbfc9cf0 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -136,12 +136,14 @@ const actionsFunc = { DEVICE_FEATURE_TYPES.LIGHT.BINARY, ); const oldValue = deviceFeature.last_value; - const timerId = setInterval(() => { - self.device.setValue(device, deviceFeature, 1); - self.device.setValue(device, deviceFeature, 0); + let newValue = 0; + let timerId = setTimeout(function blink() { + newValue = 1 - newValue; + self.device.setValue(device, deviceFeature, newValue); + timerId = setTimeout(blink, blinkingInterval); }, blinkingInterval); setTimeout(() => { - clearInterval(timerId); + clearTimeout(timerId); self.device.setValue(device, deviceFeature, oldValue); }, blinkingTime * 1000); } catch (e) { diff --git a/server/test/lib/scene/actions/scene.action.blinkLights.test.js b/server/test/lib/scene/actions/scene.action.blinkLights.test.js index 4e938f9d54..5d46abbb8b 100644 --- a/server/test/lib/scene/actions/scene.action.blinkLights.test.js +++ b/server/test/lib/scene/actions/scene.action.blinkLights.test.js @@ -55,47 +55,7 @@ describe('scene.blink-lights', () => { clock.tick(2000); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - assert.callCount(device.setValue, 5); - }); - - it('should blink light in slow mode', async () => { - const stateManager = new StateManager(event); - const deviceFeature = { - category: DEVICE_FEATURE_CATEGORIES.LIGHT, - type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, - last_value: 0, - }; - 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); - - const scope = {}; - await executeActions( - { stateManager, event, device }, - [ - [ - { - type: ACTIONS.LIGHT.BLINK, - devices: ['light-1'], - blinkingSpeed: 'slow', - blinkingTime: 2, - }, - ], - ], - scope, - ); - clock.tick(2000); - assert.calledWithExactly(device.setValue, device, deviceFeature, 0); - assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - // 2 seconds * 1 blink / sec * on/off + 1 restore - assert.callCount(device.setValue, 5); + assert.callCount(device.setValue, 2); }); it('should blink light in medium mode', async () => { @@ -135,7 +95,7 @@ describe('scene.blink-lights', () => { assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); // 2 seconds * 2 blinks / sec * on/off + 1 restore - assert.callCount(device.setValue, 9); + assert.callCount(device.setValue, 4); }); it('should blink light in fast mode', async () => { @@ -175,7 +135,7 @@ describe('scene.blink-lights', () => { assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); // 2 seconds * 10 blinks / sec * on/off + 1 restore - assert.callCount(device.setValue, 41); + assert.callCount(device.setValue, 20); }); it('should blink light in unknown mode', async () => { @@ -215,7 +175,7 @@ describe('scene.blink-lights', () => { assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); // 2 seconds * 10 blinks / sec * on/off + 1 restore - assert.callCount(device.setValue, 41); + assert.callCount(device.setValue, 20); }); it('should throw error when blinking light', async () => { From e4a80528406fd34c87e1ceea97c0116fa8c4cc46 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Sun, 28 Jan 2024 20:19:10 +0100 Subject: [PATCH 08/18] Better blinking time management --- 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 39dbfc9cf0..16c5886da1 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -145,7 +145,7 @@ const actionsFunc = { setTimeout(() => { clearTimeout(timerId); self.device.setValue(device, deviceFeature, oldValue); - }, blinkingTime * 1000); + }, blinkingTime * 1000 + blinkingInterval); } catch (e) { logger.warn(e); } From 3e40b728fa2101571da16cb7264f9aae97e81a58 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Mon, 29 Jan 2024 20:56:40 +0100 Subject: [PATCH 09/18] Changing fast blink to 200ms --- server/lib/scene/scene.actions.js | 4 ++-- .../lib/scene/actions/scene.action.blinkLights.test.js | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 16c5886da1..eca32b7bfb 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -121,10 +121,10 @@ const actionsFunc = { blinkingInterval = 500; break; case 'fast': - blinkingInterval = 100; + blinkingInterval = 200; break; default: - blinkingInterval = 100; + blinkingInterval = 200; break; } await Promise.map(action.devices, async (deviceSelector) => { diff --git a/server/test/lib/scene/actions/scene.action.blinkLights.test.js b/server/test/lib/scene/actions/scene.action.blinkLights.test.js index 5d46abbb8b..1f94293d5d 100644 --- a/server/test/lib/scene/actions/scene.action.blinkLights.test.js +++ b/server/test/lib/scene/actions/scene.action.blinkLights.test.js @@ -94,7 +94,6 @@ describe('scene.blink-lights', () => { clock.tick(2000); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - // 2 seconds * 2 blinks / sec * on/off + 1 restore assert.callCount(device.setValue, 4); }); @@ -134,8 +133,7 @@ describe('scene.blink-lights', () => { clock.tick(2000); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - // 2 seconds * 10 blinks / sec * on/off + 1 restore - assert.callCount(device.setValue, 20); + assert.callCount(device.setValue, 10); }); it('should blink light in unknown mode', async () => { @@ -174,8 +172,7 @@ describe('scene.blink-lights', () => { clock.tick(2000); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - // 2 seconds * 10 blinks / sec * on/off + 1 restore - assert.callCount(device.setValue, 20); + assert.callCount(device.setValue, 10); }); it('should throw error when blinking light', async () => { From 75a0c806b643c4090fc5581cffc8fc6fac79bb4a Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Mon, 29 Jan 2024 21:34:53 +0100 Subject: [PATCH 10/18] Fix front issues --- front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index 7454206b14..0b3a24b68b 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -34,7 +34,6 @@ class BlinkLight extends Component { this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingTime', newValue); }; handleChangeBlinkingSpeed = e => { - console.log(e.target.value); this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingSpeed', e.target.value); }; refreshSelectedOptions = nextProps => { @@ -105,7 +104,7 @@ class BlinkLight extends Component {
- From b37edb7e567766d2e8c1e4a0fadea8424f25528b Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Mon, 29 Jan 2024 21:52:45 +0100 Subject: [PATCH 11/18] Fix front style --- .../routes/scene/edit-scene/actions/BlinkLightParams.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index 0b3a24b68b..1d8d44512c 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -104,7 +104,11 @@ class BlinkLight extends Component {
- From 396a920bfcc306e5d6126963789836fa8e8e3c07 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Mon, 29 Jan 2024 22:03:52 +0100 Subject: [PATCH 12/18] Fix front style --- front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index 1d8d44512c..5c9f63ddce 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -63,7 +63,7 @@ class BlinkLight extends Component { this.refreshSelectedOptions(nextProps); } - render(props, { selectedOptions, deviceOptions, blinkingSpeed }) { + render(props, { selectedOptions, deviceOptions }) { return (
From b3e7de7cb9e6055da68eaf77612f0b3f9fb7330c Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Tue, 30 Jan 2024 22:24:29 +0100 Subject: [PATCH 13/18] Fix test coverage --- .../actions/scene.action.blinkLights.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/test/lib/scene/actions/scene.action.blinkLights.test.js b/server/test/lib/scene/actions/scene.action.blinkLights.test.js index 1f94293d5d..ae631b882c 100644 --- a/server/test/lib/scene/actions/scene.action.blinkLights.test.js +++ b/server/test/lib/scene/actions/scene.action.blinkLights.test.js @@ -52,10 +52,10 @@ describe('scene.blink-lights', () => { ], scope, ); - clock.tick(2000); + clock.tick(3200); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - assert.callCount(device.setValue, 2); + assert.callCount(device.setValue, 3); }); it('should blink light in medium mode', async () => { @@ -91,10 +91,10 @@ describe('scene.blink-lights', () => { ], scope, ); - clock.tick(2000); + clock.tick(2700); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - assert.callCount(device.setValue, 4); + assert.callCount(device.setValue, 5); }); it('should blink light in fast mode', async () => { @@ -130,10 +130,10 @@ describe('scene.blink-lights', () => { ], scope, ); - clock.tick(2000); + clock.tick(2200); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - assert.callCount(device.setValue, 10); + assert.callCount(device.setValue, 11); }); it('should blink light in unknown mode', async () => { @@ -169,10 +169,10 @@ describe('scene.blink-lights', () => { ], scope, ); - clock.tick(2000); + clock.tick(2200); assert.calledWithExactly(device.setValue, device, deviceFeature, 0); assert.calledWithExactly(device.setValue, device, deviceFeature, 1); - assert.callCount(device.setValue, 10); + assert.callCount(device.setValue, 11); }); it('should throw error when blinking light', async () => { From 843d1c566feb866aa286d199bd1e9b5543709fe4 Mon Sep 17 00:00:00 2001 From: Cyril Beslay Date: Mon, 5 Feb 2024 21:33:39 +0100 Subject: [PATCH 14/18] feat(action): Take in account PR review --- .../edit-scene/actions/BlinkLightParams.jsx | 8 +++---- server/lib/scene/scene.actions.js | 22 +++++++++++++------ server/models/scene.js | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx index 5c9f63ddce..489bc14ded 100644 --- a/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/BlinkLightParams.jsx @@ -31,10 +31,10 @@ class BlinkLight extends Component { }; handleChangeBlinkingTime = e => { let newValue = Number.isInteger(parseInt(e.target.value, 10)) ? parseInt(e.target.value, 10) : 0; - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingTime', newValue); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinking_time', newValue); }; handleChangeBlinkingSpeed = e => { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinkingSpeed', e.target.value); + this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'blinking_speed', e.target.value); }; refreshSelectedOptions = nextProps => { const selectedOptions = []; @@ -92,7 +92,7 @@ class BlinkLight extends Component { } /> @@ -106,7 +106,7 @@ class BlinkLight extends Component {