diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js index ced8261..6d13dd6 100644 --- a/smart-hut/src/components/dashboard/AutomationCreationModal.js +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -1,6 +1,6 @@ -import React, {Component, useState, useRef} from 'react'; -import {connect} from 'react-redux'; -import {RemoteService} from '../../remote'; +import React, { Component, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import { RemoteService } from '../../remote'; import update from 'immutability-helper'; import './Automations.css'; @@ -21,7 +21,7 @@ import { } from 'semantic-ui-react'; export const operands = [ - {key: 'EQUAL', text: '=', value: 'EQUAL'}, + { key: 'EQUAL', text: '=', value: 'EQUAL' }, { key: 'GREATER_EQUAL', text: '\u2265', @@ -45,8 +45,8 @@ export const operands = [ ]; const deviceStateOptions = [ - {key: 'off', text: 'off', value: false}, - {key: 'on', text: 'on', value: true}, + { key: 'off', text: 'off', value: false }, + { key: 'on', text: 'on', value: true }, ]; const CreateTrigger = (props) => { @@ -78,112 +78,112 @@ const CreateTrigger = (props) => { }; return ( - - -
- - - - - {activeOperand ? ( - <> - - props.inputChange(val)} - ref={operandsRef} - name="operand" - compact - selection - options={operands} - /> - - - { + + + + + + + + {activeOperand ? ( + <> + + props.inputChange(val)} + ref={operandsRef} + name="operand" + compact + selection + options={operands} + /> + + + { props.inputChange(val); }} - ref={valuesRef} - name="value" - type="number" - placeholder="Value" - /> - - + ref={valuesRef} + name="value" + type="number" + placeholder="Value" + /> + + ) : ( - - props.inputChange(val)} - placeholder="State" - name="on" - compact - selection - options={deviceStateOptions} - /> - + + props.inputChange(val)} + placeholder="State" + name="on" + compact + selection + options={deviceStateOptions} + /> + )} - -
-
-
+ + + + ); }; const SceneItem = (props) => { const position = props.order.indexOf(props.scene.id); return ( - - - - - - props.orderScenes(props.scene.id, val.checked)} - checked={position + 1 > 0} - /> - - -

{props.scene.name}

-
- -

{position !== -1 ? `# ${position + 1}` : ''}

-
-
-
-
-
+ + + + + + props.orderScenes(props.scene.id, val.checked)} + checked={position + 1 > 0} + /> + + +

{props.scene.name}

+
+ +

{position !== -1 ? `# ${position + 1}` : ''}

+
+
+
+
+
); }; const Trigger = ({ deviceName, trigger, onRemove, index, }) => { - const {operand, value, on} = trigger; + const { operand, value, on } = trigger; let symbol; if (operand) { symbol = operands.filter((opt) => opt.key === operand)[0].text; } return ( - - - {deviceName} - {operand ? {symbol} : ''} - {operand ? value : on ? 'on' : 'off'} - - onRemove(index)} - className="remove-icon" - name="remove" - /> - + + + {deviceName} + {operand ? {symbol} : ''} + {operand ? value : on ? 'on' : 'off'} + + onRemove(index)} + className="remove-icon" + name="remove" + /> + ); }; @@ -213,7 +213,7 @@ class AutomationSaveModal extends Component { device: trigger.deviceId, kind: trigger.kind, ...(trigger.kind === 'booleanTrigger' - ? {on: trigger.on} + ? { on: trigger.on } : { operand: trigger.operator, value: trigger.value, @@ -235,19 +235,18 @@ class AutomationSaveModal extends Component { this.orderScenes = this.orderScenes.bind(this); this.onChangeName = this.onChangeName.bind(this); - //Conditions - this.setNewCondition = this._setter("newCondition"); + // Conditions + this.setNewCondition = this._setter('newCondition'); this.addCondition = this.addCondition.bind(this); this.removeCondition = this.removeCondition.bind(this); - } openModal = (e) => { - this.setState({openModal: true}); + this.setState({ openModal: true }); }; closeModal = (e) => { - this.setState({openModal: false}); + this.setState({ openModal: false }); }; get deviceList() { @@ -255,7 +254,7 @@ class AutomationSaveModal extends Component { } _setter(property) { - return (value) => this.setState(update(this.state, {[property]: {$set: value}})); + return (value) => this.setState(update(this.state, { [property]: { $set: value } })); } triggerKind(trigger) { @@ -269,6 +268,20 @@ class AutomationSaveModal extends Component { // throw new Error("Trigger kind not handled"); } + conditionKind(condition) { + if ('operand' in condition && 'value' in condition) { + return 'rangeTrigger'; + } + if ('on' in condition) { + return 'booleanTrigger'; + } + + if ('operand' in condition && 'mode' in condition) { + return 'thermostatCondition'; + } + return false; + } + _checkNewTrigger(trigger, isCondition = false) { const error = { result: false, @@ -322,31 +335,31 @@ class AutomationSaveModal extends Component { let isNotDuplicate = null; - if(isCondition === true){ + if (isCondition === true) { isNotDuplicate = !this.state.conditionsList.some( (t) => t.device === trigger.device && t.operand === trigger.operand, ); - }else{ + } else { isNotDuplicate = !this.state.triggerList.some( (t) => t.device === trigger.device && t.operand === trigger.operand, ); } - const type = isCondition ? "condition" : "trigger" + const type = isCondition ? 'condition' : 'trigger'; const duplicationMessage = `You have already created a ${type} for this device with the same conditions`; return { result: isNotDuplicate, message: isNotDuplicate ? null - : duplicationMessage + : duplicationMessage, }; } addTrigger() { - const {result, message} = this._checkNewTrigger(this.state.newTrigger); + const { result, message } = this._checkNewTrigger(this.state.newTrigger); if (result) { this.setState( update(this.state, { - triggerList: {$push: [this.state.newTrigger]}, + triggerList: { $push: [this.state.newTrigger] }, }), ); } else { @@ -356,14 +369,14 @@ class AutomationSaveModal extends Component { removeTrigger(index) { this.setState( - update(this.state, {triggerList: {$splice: [[index, 1]]}}), + update(this.state, { triggerList: { $splice: [[index, 1]] } }), ); } // This gets triggered when the devices dropdown changes the value. onInputChange(val) { if (val.name === 'device') { - this.setNewTrigger({[val.name]: val.value}); + this.setNewTrigger({ [val.name]: val.value }); } else { this.setNewTrigger({ ...this.state.newTrigger, @@ -378,7 +391,7 @@ class AutomationSaveModal extends Component { orderScenes = (id, checked) => { if (checked) { - this.setState(update(this.state, {order: {$push: [id]}})); + this.setState(update(this.state, { order: { $push: [id] } })); } else { this.setState( update(this.state, { @@ -388,8 +401,8 @@ class AutomationSaveModal extends Component { } }; - searchScenes(_, {value}) { - this.setState(update(this.state, {scenesFilter: {$set: value}})); + searchScenes(_, { value }) { + this.setState(update(this.state, { scenesFilter: { $set: value } })); this.forceUpdate(); } @@ -452,7 +465,7 @@ class AutomationSaveModal extends Component { deviceId: trigger.device, kind, ...(kind === 'booleanTrigger' - ? {on: trigger.on} + ? { on: trigger.on } : { operator: trigger.operand, range: parseInt(trigger.value), @@ -461,6 +474,25 @@ class AutomationSaveModal extends Component { ); } + for (const condition of this.state.conditionsList) { + const kind = condition.kind || this.conditionKind(condition); + const loSpagnolo = (kind === 'thermostatCondition' ? { operator: condition.operand, mode: condition.mode } + : { + operator: condition.operand, + range: parseInt(condition.value), + }); + automation.conditions.push( + { + deviceId: condition.device, + kind, + ...(kind === 'booleanTrigger' + ? { on: condition.on } + : loSpagnolo + ), + }, + ); + } + this.props .fastUpdateAutomation(automation) .then(this.closeModal) @@ -471,6 +503,7 @@ class AutomationSaveModal extends Component { automation, triggerList: this.state.triggerList, order: this.state.order, + conditionList: this.state.conditionsList, }) .then(this.closeModal) .catch(console.error); @@ -480,18 +513,18 @@ class AutomationSaveModal extends Component { get trigger() { return this.props.id ? ( - + ); } @@ -499,11 +532,11 @@ class AutomationSaveModal extends Component { addCondition() { // Same method used to check triggers and conditions, not a mistake - const {result, message} = this._checkNewTrigger(this.state.newCondition, true); + const { result, message } = this._checkNewTrigger(this.state.newCondition, true); if (result) { this.setState( update(this.state, { - conditionsList: {$push: [this.state.newCondition]}, + conditionsList: { $push: [this.state.newCondition] }, }), ); } else { @@ -513,13 +546,13 @@ class AutomationSaveModal extends Component { removeCondition(index) { this.setState( - update(this.state, {conditionsList: {$splice: [[index, 1]]}}), + update(this.state, { conditionsList: { $splice: [[index, 1]] } }), ); } onInputChangeCondition = (val) => { if (val.name === 'device') { - this.setNewCondition({[val.name]: val.value}); + this.setNewCondition({ [val.name]: val.value }); } else { this.setNewCondition({ ...this.state.newCondition, @@ -530,150 +563,150 @@ class AutomationSaveModal extends Component { render() { return ( - - -
- {this.state.editName ? ( - + + +
+ {this.state.editName ? ( + ) : ( this.state.automationName )} -
-
+ - + <> +
+ +
+ + )} - - - -
- - - -
Add Conditions
- - {this.state.conditionsList.length > 0 +
+
+
+ + + + +
Add Conditions
+ + {this.state.conditionsList.length > 0 && this.state.conditionsList.map((condition, i) => { const deviceName = this.deviceList.filter( (d) => d.id === condition.device, )[0].name; const key = this._generateKey(condition); return ( - + ); })} - - - -
-
-
- -
+ + + + + + + + ); } } diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 81163d0..4d9e231 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -17,7 +17,7 @@ import CreateAutomation, { operands } from './AutomationCreationModal'; const Automation = ({ automation, devices, scenes, removeAutomation, }) => { - const { triggers } = automation; + const { triggers, conditions } = automation; const scenePriorities = automation.scenes; const getOperator = (operand) => operands.filter((o) => o.key === operand)[0].text; @@ -85,6 +85,36 @@ const Automation = ({ + + + +
Conditions
+ + {conditions !== undefined + && conditions.map((condition) => { + const device = devices.filter( + (d) => d.id === condition.deviceId, + )[0]; + return ( + + + {device.name} + {' '} + {condition.operator + ? `${getOperator(condition.operator) + } ${ + condition.range}` + : condition.on + ? ' - on' + : ' - off'} + + + ); + })} + +
+
+
); }; diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index e190f32..7ca4588 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -556,19 +556,30 @@ export const RemoteService = { * with user-fiendly errors as a RemoteError */ saveAutomation: (data) => { - const { automation, triggerList, order } = data; + const { + automation, triggerList, order, conditionList, +} = data; automation.triggers = []; automation.scenes = []; + automation.condition = []; return (dispatch) => { const urlAutomation = '/automation'; const urlBooleanTrigger = '/booleanTrigger'; const urlRangeTrigger = '/rangeTrigger'; const urlScenePriority = '/scenePriority'; + // conditions + const urlRangeCondition = '/rangeCondition'; + const urlBooleanCondition = '/booleanCondition'; + const urlThermostatCondition = '/thermostatCondition'; const rangeTriggerList = triggerList.filter((trigger) => 'operand' in trigger); const booleanTriggerList = triggerList.filter( (trigger) => !('operand' in trigger), ); + const rangeConditionList = conditionList.filter((condition) => 'operand' in condition && 'value' in condition); + const booleanConditionList = conditionList.filter((condition) => 'on' in condition); + const thermostatConditionList = conditionList.filter((condition) => 'operand' in condition && 'mode' in condition); + return Endpoint.post(urlAutomation, {}, automation).then( async (automationRes) => { @@ -591,7 +602,7 @@ export const RemoteService = { const trigger = { automationId: id, deviceId: t.device, - on: t.value, + on: t.on, }; resBoolTriggers.push(Endpoint.post( urlBooleanTrigger, @@ -601,6 +612,51 @@ export const RemoteService = { } automation.triggers.push(...((await Promise.all(resBoolTriggers)).map((v) => v.data))); + // Conditions + const resRangeConditions = []; + for (const t of rangeConditionList) { + const condition = { + automationId: id, + deviceId: t.device, + operator: t.operand, + range: t.value, + }; + resRangeConditions.push(Endpoint.post(urlRangeCondition, {}, condition)); + } + automation.conditions = (await Promise.all(resRangeConditions)).map((v) => v.data); + + const resBoolConditions = []; + for (const t of booleanConditionList) { + console.log('HERE', t); + const condition = { + automationId: id, + deviceId: t.device, + on: t.on, + }; + resBoolConditions.push(Endpoint.post( + urlBooleanCondition, + {}, + condition, + )); + } + automation.conditions.push(...((await Promise.all(resBoolConditions)).map((v) => v.data))); + + const resThermoConditions = []; + for (const t of thermostatConditionList) { + const condition = { + automationId: id, + deviceId: t.device, + mode: t.mode, + operator: t.operand, + }; + resThermoConditions.push(Endpoint.post( + urlThermostatCondition, + {}, + condition, + )); + } + automation.conditions.push(...((await Promise.all(resThermoConditions)).map((v) => v.data))); + const resScenePriorities = []; for (const [priority, sceneId] of order.entries()) { const scenePriority = {