Skip to content

Commit

Permalink
Scene: add new action send zigbee2mqtt msg (#2160)
Browse files Browse the repository at this point in the history
  • Loading branch information
William-De71 authored Nov 11, 2024
1 parent 68cdb6c commit adc1df0
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 0 deletions.
10 changes: 10 additions & 0 deletions front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,13 @@
"variablesExplanation": "Um eine Variable im Text einzufügen, gib \"{{\" ein. Um einen Variablenwert festzulegen, musst du vor diesem Feld das das Feld \"Gerätewert abrufen\" verwenden.",
"messagePlaceholder": "Meine Message"
},
"zigbee2mqttMessage": {
"topic": "Topic",
"topicPlaceholder": "/gladys/mein-topic",
"messageLabel": "Message",
"variablesExplanation": "Um eine Variable im Text einzufügen, gib \"{{\" ein. Um einen Variablenwert festzulegen, musst du vor diesem Feld das das Feld \"Gerätewert abrufen\" verwenden.",
"messagePlaceholder": "Meine Message"
},
"playNotification": {
"description": "Diese Aktion lässt Gladys auf dem ausgewählten Lautsprecher sprechen.",
"needGladysPlus": "Erfordert Gladys Plus, da die Text-to-Speech-APIs kostenpflichtig sind.",
Expand Down Expand Up @@ -2025,6 +2032,9 @@
"mqtt": {
"send": "MQTT-Message senden"
},
"zigbee2mqtt": {
"send": "Zigbee2mqtt-Message senden"
},
"music": {
"play-notification": "Auf einem Lautsprecher sprechen"
},
Expand Down
10 changes: 10 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,13 @@
"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"
},
"zigbee2mqttMessage": {
"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"
},
"playNotification": {
"description": "This action will make Gladys speak on the selected speaker.",
"needGladysPlus": "Requires Gladys Plus as Text-To-Speech APIs are paid.",
Expand Down Expand Up @@ -2025,6 +2032,9 @@
"mqtt": {
"send": "Send MQTT Message"
},
"zigbee2mqtt": {
"send": "Send Zigbee2mqtt Message"
},
"music": {
"play-notification": "Talk on a speaker"
},
Expand Down
10 changes: 10 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,13 @@
"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"
},
"zigbee2mqttMessage": {
"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"
},
"playNotification": {
"description": "Cette action fera parler Gladys sur l'enceinte sélectionnée.",
"needGladysPlus": "Nécessite Gladys Plus car les API de \"Text To Speech\" sont payantes.",
Expand Down Expand Up @@ -2025,6 +2032,9 @@
"mqtt": {
"send": "Envoyer un message MQTT"
},
"zigbee2mqtt": {
"send": "Envoyer un message Zigbee2mqtt"
},
"music": {
"play-notification": "Parler sur une enceinte"
},
Expand Down
14 changes: 14 additions & 0 deletions front/src/routes/scene/edit-scene/ActionCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import SendMessageCameraParams from './actions/SendMessageCameraParams';
import CheckAlarmMode from './actions/CheckAlarmMode';
import SetAlarmMode from './actions/SetAlarmMode';
import SendMqttMessage from './actions/SendMqttMessage';
import SendZigbee2MqttMessage from './actions/SendZigbee2MqttMessage';
import PlayNotification from './actions/PlayNotification';
import EdfTempoCondition from './actions/EdfTempoCondition';
import AskAI from './actions/AskAI';
Expand Down Expand Up @@ -66,6 +67,7 @@ const ACTION_ICON = {
[ACTIONS.ALARM.SET_ALARM_MODE]: 'fe fe-bell',
[ACTIONS.MQTT.SEND]: 'fe fe-message-square',
[ACTIONS.MUSIC.PLAY_NOTIFICATION]: 'fe fe-speaker',
[ACTIONS.ZIGBEE2MQTT.SEND]: 'fe fe-message-square',
[ACTIONS.AI.ASK]: 'fe fe-cpu'
};

Expand Down Expand Up @@ -104,6 +106,7 @@ const ActionCard = ({ children, ...props }) => {
props.action.type === ACTIONS.MESSAGE.SEND ||
props.action.type === ACTIONS.CALENDAR.IS_EVENT_RUNNING ||
props.action.type === ACTIONS.MQTT.SEND ||
props.action.type === ACTIONS.ZIGBEE2MQTT.SEND ||
props.action.type === ACTIONS.LIGHT.BLINK,
'col-lg-4':
props.action.type !== ACTIONS.CONDITION.ONLY_CONTINUE_IF &&
Expand Down Expand Up @@ -392,6 +395,17 @@ const ActionCard = ({ children, ...props }) => {
triggersVariables={props.triggersVariables}
/>
)}
{props.action.type === ACTIONS.ZIGBEE2MQTT.SEND && (
<SendZigbee2MqttMessage
action={props.action}
columnIndex={props.columnIndex}
index={props.index}
updateActionProperty={props.updateActionProperty}
actionsGroupsBefore={props.actionsGroupsBefore}
variables={props.variables}
triggersVariables={props.triggersVariables}
/>
)}
{props.action.type === ACTIONS.MUSIC.PLAY_NOTIFICATION && (
<PlayNotification
action={props.action}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const ACTION_LIST = [
ACTIONS.ALARM.CHECK_ALARM_MODE,
ACTIONS.ALARM.SET_ALARM_MODE,
ACTIONS.MQTT.SEND,
ACTIONS.ZIGBEE2MQTT.SEND,
ACTIONS.MUSIC.PLAY_NOTIFICATION,
ACTIONS.AI.ASK
];
Expand Down
Original file line number Diff line number Diff line change
@@ -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 SendZigbee2MqttMessage 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.zigbee2mqttMessage.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.zigbee2mqttMessage.topicPlaceholder" />}
/>
</Localizer>
</div>
<div class="form-group">
<label class="form-label">
<Text id="editScene.actionsCard.zigbee2mqttMessage.messageLabel" />
</label>
<div style={helpTextStyle}>
<Text id="editScene.actionsCard.zigbee2mqttMessage.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.zigbee2mqttMessage.messagePlaceholder" />}
/>
</Localizer>
</div>
</form>
</div>
);
}
}

export default connect('httpClient', {})(SendZigbee2MqttMessage);
8 changes: 8 additions & 0 deletions server/lib/scene/scene.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,14 @@ const actionsFunc = {
mqttService.device.publish(action.topic, messageWithVariables);
}
},
[ACTIONS.ZIGBEE2MQTT.SEND]: (self, action, scope) => {
const zigbee2mqttService = self.service.getService('zigbee2mqtt');

if (zigbee2mqttService) {
const messageWithVariables = Handlebars.compile(action.message)(scope);
zigbee2mqttService.device.publish(action.topic, messageWithVariables);
}
},
[ACTIONS.MUSIC.PLAY_NOTIFICATION]: async (self, action, scope) => {
// Get device
const device = self.stateManager.get('device', action.device);
Expand Down
2 changes: 2 additions & 0 deletions server/services/zigbee2mqtt/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { getConfiguration } = require('./getConfiguration');
const { saveConfiguration } = require('./saveConfiguration');
const { disconnect } = require('./disconnect');
const { handleMqttMessage } = require('./handleMqttMessage');
const { publish } = require('./publish');
const { getDiscoveredDevices } = require('./getDiscoveredDevices');
const { findMatchingExpose } = require('./findMatchingExpose');
const { readValue } = require('./readValue');
Expand Down Expand Up @@ -70,6 +71,7 @@ Zigbee2mqttManager.prototype.getConfiguration = getConfiguration;
Zigbee2mqttManager.prototype.saveConfiguration = saveConfiguration;
Zigbee2mqttManager.prototype.disconnect = disconnect;
Zigbee2mqttManager.prototype.handleMqttMessage = handleMqttMessage;
Zigbee2mqttManager.prototype.publish = publish;
Zigbee2mqttManager.prototype.getDiscoveredDevices = getDiscoveredDevices;
Zigbee2mqttManager.prototype.findMatchingExpose = findMatchingExpose;
Zigbee2mqttManager.prototype.readValue = readValue;
Expand Down
24 changes: 24 additions & 0 deletions server/services/zigbee2mqtt/lib/publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const logger = require('../../../utils/logger');
const { ServiceNotConfiguredError } = require('../../../utils/coreErrors');

/**
* @description Publish a MQTT message.
* @param {string} topic - MQTT Topic.
* @param {string} message - MQTT message.
* @example zigbee2mqtt.publish('zigbee2mqtt/test', '{}');
*/
function publish(topic, message) {
if (!this.mqttClient) {
throw new ServiceNotConfiguredError('MQTT is not configured.');
}
logger.debug(`Publishing MQTT message on topic ${topic}`);
this.mqttClient.publish(topic, message, undefined, (err) => {
if (err) {
logger.warn(`MQTT - Error publishing to ${topic} : ${err}`);
}
});
}

module.exports = {
publish,
};
Original file line number Diff line number Diff line change
@@ -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-zigbee2mqtt-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 zigbee2MqttService = {
device: {
publish: fake.resolves(null),
},
};
const service = {
getService: fake.returns(zigbee2MqttService),
};
const scope = {};
await executeActions(
{ stateManager, event, service },
[
[
{
type: ACTIONS.DEVICE.GET_VALUE,
device_feature: 'my-device-feature',
},
],
[
{
type: ACTIONS.ZIGBEE2MQTT.SEND,
topic: '/my/mqtt/topic',
message: 'Temperature in the living room is {{0.0.last_value}} °C.',
},
],
],
scope,
);
assert.calledWith(zigbee2MqttService.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 zigbee2MqttService = {
device: {
publish: fake.resolves(null),
},
};
const service = {
getService: fake.returns(zigbee2MqttService),
};
const scope = {};
await executeActions(
{ stateManager, event, service, http },
[
[
{
type: ACTIONS.HTTP.REQUEST,
method: 'post',
url: 'http://test.test',
body: '{"toto":"toto"}',
headers: [],
},
],
[
{
type: ACTIONS.ZIGBEE2MQTT.SEND,
topic: '/my/mqtt/topic',
message: 'Temperature in the living room is {{0.0.result.[0]}} °C.',
},
],
],
scope,
);
assert.calledWith(zigbee2MqttService.device.publish, '/my/mqtt/topic', 'Temperature in the living room is 15 °C.');
});
});
Loading

0 comments on commit adc1df0

Please sign in to comment.