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

Scene: add new action send zigbee2mqtt msg #2160

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,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 @@ -2024,6 +2031,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 @@ -1956,6 +1956,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 @@ -2024,6 +2031,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 @@ -1956,6 +1956,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 @@ -2024,6 +2031,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
Loading