Skip to content

Commit

Permalink
Add scene trigger : New MQTT message (#1913)
Browse files Browse the repository at this point in the history
  • Loading branch information
callemand authored Nov 10, 2023
1 parent 690773d commit 30ce850
Show file tree
Hide file tree
Showing 18 changed files with 329 additions and 11 deletions.
11 changes: 11 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,9 @@
},
"system": {
"start": "Gladys starting"
},
"mqtt": {
"received": "Receive message from MQTT"
}
},
"triggersCard": {
Expand Down Expand Up @@ -1786,6 +1789,14 @@
},
"gladysStart": {
"description": "This scene will be executed each time Gladys starts"
},
"mqttReceived": {
"description": "This scene will run when a message is received in the selected MQTT topic",
"topicLabel": "Topic",
"topicPlaceholder": "Enter your topic",
"messageLabel": "Message",
"messagePlaceholder": "Enter the expected message",
"messageDescription": "Enter the expected message or leave blank to run the scene regardless of the message"
}
}
},
Expand Down
11 changes: 11 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,9 @@
},
"system": {
"start": "Gladys démarre"
},
"mqtt": {
"received": "Recevoir un message MQTT"
}
},
"triggersCard": {
Expand Down Expand Up @@ -1787,6 +1790,14 @@
},
"gladysStart": {
"description": "Cette scène sera exécutée à chaque démarrage de Gladys"
},
"mqttReceived": {
"description": "Cette scène s'exécutera lorsque un message sera reçu dans le topic MQTT sélectionné",
"topicLabel": "Topic",
"topicPlaceholder": "Entrez votre topic",
"messageLabel": "Message",
"messagePlaceholder": "Entrez le message attendu",
"messageDescription": "Entrez le message attendu ou laissez vide pour exécuter la scène quelque soit le message"
}
}
},
Expand Down
12 changes: 10 additions & 2 deletions front/src/routes/scene/edit-scene/TriggerCard.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { h } from 'preact';
import { Text } from 'preact-i18n';
import cx from 'classnames';

Expand All @@ -11,6 +10,7 @@ import HouseEmptyOrNot from './triggers/HouseEmptyOrNot';
import UserEnteredOrLeftArea from './triggers/UserEnteredOrLeftArea';
import CalendarEventIsComing from './triggers/CalendarEventIsComing';
import AlarmModeTrigger from './triggers/AlarmModeTrigger';
import MQTTReceivedTrigger from './triggers/MQTTReceivedTrigger';

import { EVENTS } from '../../../../../server/utils/constants';
import GladysStartTrigger from './triggers/GladysStartTrigger';
Expand All @@ -32,7 +32,8 @@ const TRIGGER_ICON = {
[EVENTS.ALARM.DISARM]: 'fe-bell-off',
[EVENTS.ALARM.PANIC]: 'fe-alert-triangle',
[EVENTS.ALARM.TOO_MANY_CODES_TESTS]: 'fe-alert-triangle',
[EVENTS.SYSTEM.START]: 'fe-activity'
[EVENTS.SYSTEM.START]: 'fe-activity',
[EVENTS.MQTT.RECEIVED]: 'fe-hash'
};

const ALARM_TRIGGERS = [
Expand Down Expand Up @@ -166,6 +167,13 @@ const TriggerCard = ({ children, ...props }) => (
trigger={props.trigger}
/>
)}
{props.trigger.type === EVENTS.MQTT.RECEIVED && (
<MQTTReceivedTrigger
updateTriggerProperty={props.updateTriggerProperty}
index={props.index}
trigger={props.trigger}
/>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const TRIGGER_LIST = [
EVENTS.ALARM.PANIC,
EVENTS.ALARM.PARTIAL_ARM,
EVENTS.ALARM.TOO_MANY_CODES_TESTS,
EVENTS.SYSTEM.START
EVENTS.SYSTEM.START,
EVENTS.MQTT.RECEIVED
];

class ChooseTriggerType extends Component {
Expand Down
56 changes: 56 additions & 0 deletions front/src/routes/scene/edit-scene/triggers/MQTTReceivedTrigger.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Component } from 'preact';
import { connect } from 'unistore/preact';
import { Text, Localizer } from 'preact-i18n';

class MQTTReceived extends Component {
updateTopicName = e => {
this.props.updateTriggerProperty(this.props.index, 'topic', e.target.value);
};

updateMessage = e => {
this.props.updateTriggerProperty(this.props.index, 'message', e.target.value);
};

render({}, {}) {
return (
<div>
<p>
<Text id="editScene.triggersCard.mqttReceived.description" />
</p>
<div class="form-group">
<label className="form-label">
<Text id="editScene.triggersCard.mqttReceived.topicLabel" />
</label>
<Localizer>
<input
type="text"
value={this.props.trigger.topic}
onInput={this.updateTopicName}
className="form-control"
placeholder={<Text id="editScene.triggersCard.mqttReceived.topicPlaceholder" />}
/>
</Localizer>
</div>
<div class="form-group">
<label className="form-label">
<Text id="editScene.triggersCard.mqttReceived.messageLabel" />
</label>
<div class="mb-1 small">
<Text id="editScene.triggersCard.mqttReceived.messageDescription" />
</div>
<Localizer>
<input
type="text"
value={this.props.trigger.message}
onInput={this.updateMessage}
className="form-control"
placeholder={<Text id="editScene.triggersCard.mqttReceived.messagePlaceholder" />}
/>
</Localizer>
</div>
</div>
);
}
}

export default connect('httpClient,user', {})(MQTTReceived);
1 change: 1 addition & 0 deletions server/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ function Gladys(params = {}) {
gateway,
scheduler,
brain,
service,
);

const gladys = {
Expand Down
2 changes: 2 additions & 0 deletions server/lib/scene/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const SceneManager = function SceneManager(
gateway,
scheduler,
brain,
service,
) {
this.stateManager = stateManager;
this.event = event;
Expand All @@ -45,6 +46,7 @@ const SceneManager = function SceneManager(
this.http = http;
this.gateway = gateway;
this.brain = brain;
this.service = service;
this.scenes = {};
this.timezone = DEFAULT_TIMEZONE;
// @ts-ignore
Expand Down
16 changes: 16 additions & 0 deletions server/lib/scene/scene.addScene.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ function addScene(sceneRaw) {
}
trigger.jsInterval = setInterval(() => this.event.emit(EVENTS.TRIGGERS.CHECK, trigger), intervalMilliseconds);
}

if (trigger.type === EVENTS.MQTT.RECEIVED) {
const mqttService = this.service.getService('mqtt');

if (mqttService) {
trigger.mqttCallback = (topic, message) => {
this.event.emit(EVENTS.TRIGGERS.CHECK, {
type: EVENTS.MQTT.RECEIVED,
topic,
message,
});
};

mqttService.device.subscribe(trigger.topic, trigger.mqttCallback);
}
}
});
}

Expand Down
6 changes: 6 additions & 0 deletions server/lib/scene/scene.cancelTriggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ function cancelTriggers(sceneSelector) {
clearInterval(trigger.jsInterval);
delete trigger.jsInterval;
}
if (trigger.topic) {
const mqttService = this.service.getService('mqtt');
if (mqttService) {
mqttService.device.unsubscribe(trigger.topic);
}
}
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions server/lib/scene/scene.triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const triggersFunc = {
[EVENTS.ALARM.PANIC]: (event, trigger) => event.house === trigger.house,
[EVENTS.ALARM.TOO_MANY_CODES_TESTS]: (event, trigger) => event.house === trigger.house,
[EVENTS.SYSTEM.START]: () => true,
[EVENTS.MQTT.RECEIVED]: (event, trigger) =>
event.topic === trigger.topic && (trigger.message === '' || trigger.message === event.message),
};

module.exports = {
Expand Down
2 changes: 2 additions & 0 deletions server/models/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ const triggersSchema = Joi.array().items(
.min(1)
.max(31),
threshold_only: Joi.boolean(),
topic: Joi.string(),
message: Joi.string().allow(''),
}),
);

Expand Down
3 changes: 2 additions & 1 deletion server/services/usb/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion server/services/usb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"cpu": [
"x64",
"arm",
"arm64"
"arm64",
"arm64e"
],
"scripts": {},
"dependencies": {
Expand Down
35 changes: 34 additions & 1 deletion server/test/lib/scene/scene.addScene.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { expect } = require('chai');
const sinon = require('sinon');

const { fake, assert } = sinon;

const { EVENTS } = require('../../../utils/constants');
const { BadParameters } = require('../../../utils/coreErrors');
const SceneManager = require('../../../lib/scene');
Expand All @@ -10,6 +11,10 @@ describe('SceneManager.addScene', () => {
const house = {};
const event = {};
const brain = {};
const service = {};
const mqttService = {
device: {},
};

let sceneManager;

Expand All @@ -19,6 +24,8 @@ describe('SceneManager.addScene', () => {
event.emit = fake.returns(null);
brain.addNamedEntity = fake.returns(null);
brain.removeNamedEntity = fake.returns(null);
mqttService.device.subscribe = fake.returns(null);
service.getService = fake.returns(mqttService);

const scheduler = {
scheduleJob: (date, callback) => {
Expand All @@ -30,7 +37,7 @@ describe('SceneManager.addScene', () => {
},
};

sceneManager = new SceneManager({}, event, {}, {}, {}, house, {}, {}, {}, scheduler, brain);
sceneManager = new SceneManager({}, event, {}, {}, {}, house, {}, {}, {}, scheduler, brain, service);
});

afterEach(() => {
Expand Down Expand Up @@ -229,4 +236,30 @@ describe('SceneManager.addScene', () => {
});
expect(sceneManager.scenes[scene.selector].triggers[0]).to.not.have.property('nodeScheduleJob');
});
it('should add a scene with a message received trigger', async () => {
const scene = sceneManager.addScene({
name: 'a-test-scene',
icon: 'bell',
active: true,
triggers: [
{
type: EVENTS.MQTT.RECEIVED,
topic: 'my/topic',
},
],
actions: [],
});
expect(sceneManager.scenes[scene.selector].triggers[0]).to.not.have.property('nodeScheduleJob');
expect(sceneManager.scenes[scene.selector].triggers[0]).to.not.have.property('jsInterval');
expect(sceneManager.scenes[scene.selector].triggers[0]).to.have.property('mqttCallback');
assert.calledWithExactly(service.getService, 'mqtt');
assert.calledOnce(mqttService.device.subscribe);

sceneManager.scenes[scene.selector].triggers[0].mqttCallback('my/topic', 'message');
assert.calledOnceWithExactly(event.emit, EVENTS.TRIGGERS.CHECK, {
type: EVENTS.MQTT.RECEIVED,
topic: 'my/topic',
message: 'message',
});
});
});
35 changes: 31 additions & 4 deletions server/test/lib/scene/scene.cancelTriggers.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
const { expect } = require('chai');
const EventEmitter = require('events');
const sinon = require('sinon');
const { EVENTS } = require('../../../utils/constants');
const SceneManager = require('../../../lib/scene');

const { fake } = sinon;
const event = new EventEmitter();
const { fake, assert } = sinon;

describe('SceneManager.cancelTriggers', () => {
let sceneManager;

const brain = {};
const service = {};
const event = {};
const mqttService = {
device: {},
};

beforeEach(() => {
mqttService.device.unsubscribe = fake.returns(null);
mqttService.device.subscribe = fake.returns(null);
service.getService = fake.returns(mqttService);

const house = {
get: fake.resolves([]),
};
Expand All @@ -30,7 +37,9 @@ describe('SceneManager.cancelTriggers', () => {
brain.addNamedEntity = fake.returns(null);
brain.removeNamedEntity = fake.returns(null);

sceneManager = new SceneManager({}, event, {}, {}, {}, house, {}, {}, {}, scheduler, brain);
event.on = fake.returns(null);

sceneManager = new SceneManager({}, event, {}, {}, {}, house, {}, {}, {}, scheduler, brain, service);
});

afterEach(() => {
Expand Down Expand Up @@ -74,4 +83,22 @@ describe('SceneManager.cancelTriggers', () => {
sceneManager.cancelTriggers(scene.selector);
expect(sceneManager.scenes[scene.selector].triggers[0]).not.to.have.property('jsInterval');
});
it('should cancel a message received trigger', async () => {
const scene = await sceneManager.create({
name: 'a-test-scene',
icon: 'bell',
triggers: [
{
type: EVENTS.MQTT.RECEIVED,
topic: 'my/topic',
},
],
actions: [],
});
expect(sceneManager.scenes[scene.selector].triggers[0]).not.to.have.property('nodeScheduleJob');
expect(sceneManager.scenes[scene.selector].triggers[0]).not.to.have.property('jsInterval');
sceneManager.cancelTriggers(scene.selector);
assert.calledWithExactly(service.getService, 'mqtt');
assert.calledWithExactly(mqttService.device.unsubscribe, 'my/topic');
});
});
Loading

0 comments on commit 30ce850

Please sign in to comment.