Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add action send mqtt message #1945

Merged
merged 4 commits into from Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions front/src/config/i18n/en.json
Expand Up @@ -1591,6 +1591,13 @@
"description": "This action will set the selected house to the selected alarm mode.",
"houseLabel": "House",
"alarmModeLabel": "Alarm Mode"
},
"mqttMessage": {
"topic": "Topic",
"topicPlaceholder": "/gladys/my-topic",
"messageLabel": "Message",
"variablesExplanation": "To inject a variable in the text, press '{{ '. To set a variable value, you need to use the 'Get device value' box before this one.",
"messagePlaceholder": "My message"
}
},
"actions": {
Expand Down Expand Up @@ -1645,6 +1652,9 @@
"alarm": {
"check-alarm-mode": "If the alarm is in mode",
"set-alarm-mode": "Set alarm mode to"
},
"mqtt": {
"send": "Send MQTT Message"
}
},
"variables": {
Expand Down
10 changes: 10 additions & 0 deletions front/src/config/i18n/fr.json
Expand Up @@ -1592,6 +1592,13 @@
"description": "Cette action passera la maison sélectionnée dans le mode d'alarme sélectionné.",
"houseLabel": "Maison",
"alarmModeLabel": "Mode de l'alarme"
},
"mqttMessage": {
"topic": "Topic",
"topicPlaceholder": "/gladys/my-topic",
"messageLabel": "Message",
"variablesExplanation": "Pour injecter une variable, tapez '{{ '. Attention, vous devez avoir défini une variable auparavant dans une action 'Récupérer le dernier état' placé avant ce bloc message.",
"messagePlaceholder": "Mon message"
}
},
"actions": {
Expand Down Expand Up @@ -1646,6 +1653,9 @@
"alarm": {
"check-alarm-mode": "Si l'alarme est en mode",
"set-alarm-mode": "Passer l'alarme en mode"
},
"mqtt": {
"send": "Envoyer un message MQTT"
}
},
"variables": {
Expand Down
19 changes: 17 additions & 2 deletions front/src/routes/scene/edit-scene/ActionCard.jsx
Expand Up @@ -27,6 +27,7 @@ import EcowattCondition from './actions/EcowattCondition';
import SendMessageCameraParams from './actions/SendMessageCameraParams';
import CheckAlarmMode from './actions/CheckAlarmMode';
import SetAlarmMode from './actions/SetAlarmMode';
import SendMqttMessage from './actions/SendMqttMessage';

const deleteActionFromColumn = (columnIndex, rowIndex, deleteAction) => () => {
deleteAction(columnIndex, rowIndex);
Expand Down Expand Up @@ -56,7 +57,8 @@ const ACTION_ICON = {
[ACTIONS.CALENDAR.IS_EVENT_RUNNING]: 'fe fe-calendar',
[ACTIONS.ECOWATT.CONDITION]: 'fe fe-zap',
[ACTIONS.ALARM.CHECK_ALARM_MODE]: 'fe fe-bell',
[ACTIONS.ALARM.SET_ALARM_MODE]: 'fe fe-bell'
[ACTIONS.ALARM.SET_ALARM_MODE]: 'fe fe-bell',
[ACTIONS.MQTT.SEND]: 'fe fe-message-square'
};

const ACTION_CARD_TYPE = 'ACTION_CARD_TYPE';
Expand Down Expand Up @@ -91,7 +93,9 @@ const ActionCard = ({ children, ...props }) => {
class={cx({
'col-lg-12': props.action.type === ACTIONS.CONDITION.ONLY_CONTINUE_IF,
'col-lg-6':
props.action.type === ACTIONS.MESSAGE.SEND || props.action.type === ACTIONS.CALENDAR.IS_EVENT_RUNNING,
props.action.type === ACTIONS.MESSAGE.SEND ||
props.action.type === ACTIONS.CALENDAR.IS_EVENT_RUNNING ||
props.action.type === ACTIONS.MQTT.SEND,
'col-lg-4':
props.action.type !== ACTIONS.CONDITION.ONLY_CONTINUE_IF &&
props.action.type !== ACTIONS.MESSAGE.SEND &&
Expand Down Expand Up @@ -352,6 +356,17 @@ const ActionCard = ({ children, ...props }) => {
updateActionProperty={props.updateActionProperty}
/>
)}
{props.action.type === ACTIONS.MQTT.SEND && (
<SendMqttMessage
action={props.action}
columnIndex={props.columnIndex}
index={props.index}
updateActionProperty={props.updateActionProperty}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
triggersVariables={props.triggersVariables}
/>
)}
</div>
</div>
</div>
Expand Down
Expand Up @@ -29,7 +29,8 @@ const ACTION_LIST = [
ACTIONS.CALENDAR.IS_EVENT_RUNNING,
ACTIONS.ECOWATT.CONDITION,
ACTIONS.ALARM.CHECK_ALARM_MODE,
ACTIONS.ALARM.SET_ALARM_MODE
ACTIONS.ALARM.SET_ALARM_MODE,
ACTIONS.MQTT.SEND
];

const TRANSLATIONS = ACTION_LIST.reduce((acc, action) => {
Expand Down
66 changes: 66 additions & 0 deletions front/src/routes/scene/edit-scene/actions/SendMqttMessage.jsx
@@ -0,0 +1,66 @@
import { Component } from 'preact';
import { connect } from 'unistore/preact';
import { Text, Localizer } from 'preact-i18n';

import TextWithVariablesInjected from '../../../../components/scene/TextWithVariablesInjected';

const helpTextStyle = {
fontSize: 12,
marginBottom: '.375rem'
};

class SendMqttMessage extends Component {
handleChangeTopic = e => {
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'topic', e.target.value);
};
handleChangeMessage = text => {
const newMessage = text && text.length > 0 ? text : undefined;
this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'message', newMessage);
};

render(props) {
return (
<div>
<form>
<div class="form-group">
<label class="form-label">
<Text id="editScene.actionsCard.mqttMessage.topic" />
<span class="form-required">
<Text id="global.requiredField" />
</span>
</label>
<Localizer>
<input
type="text"
class="form-control"
value={props.action.topic}
onChange={this.handleChangeTopic}
placeholder={<Text id="editScene.actionsCard.mqttMessage.topicPlaceholder" />}
/>
</Localizer>
</div>
<div class="form-group">
<label class="form-label">
<Text id="editScene.actionsCard.mqttMessage.messageLabel" />
</label>
<div style={helpTextStyle}>
<Text id="editScene.actionsCard.mqttMessage.variablesExplanation" />
</div>
<Localizer>
<TextWithVariablesInjected
text={props.action.message}
updateText={this.handleChangeMessage}
triggersVariables={props.triggersVariables}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
placeholder={<Text id="editScene.actionsCard.mqttMessage.messagePlaceholder" />}
/>
</Localizer>
</div>
</form>
</div>
);
}
}

export default connect('httpClient', {})(SendMqttMessage);
8 changes: 8 additions & 0 deletions server/lib/scene/scene.actions.js
Expand Up @@ -457,6 +457,14 @@ const actionsFunc = {
await self.house.panic(action.house);
}
},
[ACTIONS.MQTT.SEND]: (self, action, scope) => {
const mqttService = self.service.getService('mqtt');

if (mqttService) {
const messageWithVariables = Handlebars.compile(action.message)(scope);
mqttService.device.publish(action.topic, messageWithVariables);
}
},
};

module.exports = {
Expand Down
2 changes: 2 additions & 0 deletions server/models/scene.js
Expand Up @@ -58,6 +58,8 @@ const actionSchema = Joi.array().items(
evaluate_value: Joi.string(),
}),
alarm_mode: Joi.string().valid(...ALARM_MODES_LIST),
topic: Joi.string(),
message: Joi.string().allow(''),
}),
),
);
Expand Down
87 changes: 87 additions & 0 deletions server/test/lib/scene/actions/scene.action.sendMqttMessage.test.js
@@ -0,0 +1,87 @@
const { fake, assert } = require('sinon');
const EventEmitter = require('events');

const { ACTIONS } = require('../../../../utils/constants');
const { executeActions } = require('../../../../lib/scene/scene.executeActions');

const StateManager = require('../../../../lib/state');

const event = new EventEmitter();

describe('scene.send-mqtt-message', () => {
it('should send message with value injected from device get-value', async () => {
const stateManager = new StateManager(event);
stateManager.setState('deviceFeature', 'my-device-feature', {
category: 'light',
type: 'binary',
last_value: 15,
});
const mqttService = {
device: {
publish: fake.resolves(null),
},
};
const service = {
getService: fake.returns(mqttService),
};
const scope = {};
await executeActions(
{ stateManager, event, service },
[
[
{
type: ACTIONS.DEVICE.GET_VALUE,
device_feature: 'my-device-feature',
},
],
[
{
type: ACTIONS.MQTT.SEND,
topic: '/my/mqtt/topic',
message: 'Temperature in the living room is {{0.0.last_value}} °C.',
},
],
],
scope,
);
assert.calledWith(mqttService.device.publish, '/my/mqtt/topic', 'Temperature in the living room is 15 °C.');
});
it('should send message with value injected from http-request', async () => {
const stateManager = new StateManager(event);
const http = {
request: fake.resolves({ result: [15], error: null }),
};
const mqttService = {
device: {
publish: fake.resolves(null),
},
};
const service = {
getService: fake.returns(mqttService),
};
const scope = {};
await executeActions(
{ stateManager, event, service, http },
[
[
{
type: ACTIONS.HTTP.REQUEST,
method: 'post',
url: 'http://test.test',
body: '{"toto":"toto"}',
headers: [],
},
],
[
{
type: ACTIONS.MQTT.SEND,
topic: '/my/mqtt/topic',
message: 'Temperature in the living room is {{0.0.result.[0]}} °C.',
},
],
],
scope,
);
assert.calledWith(mqttService.device.publish, '/my/mqtt/topic', 'Temperature in the living room is 15 °C.');
});
});
3 changes: 3 additions & 0 deletions server/utils/constants.js
Expand Up @@ -348,6 +348,9 @@ const ACTIONS = {
ECOWATT: {
CONDITION: 'ecowatt.condition',
},
MQTT: {
SEND: 'mqtt.send',
},
};

const INTENTS = {
Expand Down