Skip to content

Commit

Permalink
MQTT : Add custom topic for device features (#2066)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Gilles authored May 10, 2024
1 parent 3bd6128 commit 88e10e2
Show file tree
Hide file tree
Showing 31 changed files with 950 additions and 8 deletions.
2 changes: 2 additions & 0 deletions front/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import LANManagerSettingsPage from '../routes/integration/all/lan-manager/settin
import MqttDevicePage from '../routes/integration/all/mqtt/device-page';
import MqttDeviceSetupPage from '../routes/integration/all/mqtt/device-page/setup';
import MqttSetupPage from '../routes/integration/all/mqtt/setup-page';
import MqttDebugPage from '../routes/integration/all/mqtt/debug-page/Debug';

// Zigbee2mqtt
import Zigbee2mqttPage from '../routes/integration/all/zigbee2mqtt/device-page';
Expand Down Expand Up @@ -262,6 +263,7 @@ const AppRouter = connect(
<MqttDeviceSetupPage path="/dashboard/integration/device/mqtt/edit" />
<MqttDeviceSetupPage path="/dashboard/integration/device/mqtt/edit/:deviceSelector" />
<MqttSetupPage path="/dashboard/integration/device/mqtt/setup" />
<MqttDebugPage path="/dashboard/integration/device/mqtt/debug" />
<Zigbee2mqttPage path="/dashboard/integration/device/zigbee2mqtt" />
<Zigbee2mqttDiscoverPage path="/dashboard/integration/device/zigbee2mqtt/discover" />
<Zigbee2mqttSetupPage path="/dashboard/integration/device/zigbee2mqtt/setup" />
Expand Down
16 changes: 16 additions & 0 deletions front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,7 @@
"title": "MQTT",
"description": "Verbinde dich mit einem lokalen oder entfernten MQTT-Server.",
"deviceTab": "Meine Geräte",
"debugTab": "Debug MQTT",
"setupTab": "Einrichtung",
"documentation": "MQTT-Dokumentation",
"apiDocumentation": "MQTT-API-Dokumentation ",
Expand Down Expand Up @@ -1235,7 +1236,13 @@
"mqttTopicToPublishExampleDescription": "Gladys hört auf dieses Topic. Wenn du einen Wert in diesem Thema veröffentlichst, wird er diesem Gerät zugeordnet. (siehe <a href=\"https://gladysassistant.com/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">Dokumentation</a>)",
"mqttTopicToListenExampleLabel": "MQTT-Hörensthema",
"mqttTopicToListenExampleDescription": "Gladys veröffentlicht in diesem Topic, wenn das Gerät in Gladys gesteuert wird. Du musst diesem Topic zuhören. (siehe <a href=\"https://gladysassistant.com/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">Dokumentation</a>)",
"mqttCustomTopic": "Benutzerdefiniertes MQTT-Thema",
"mqttCustomTopicDescription": "Gladys bietet eine Standard-MQTT-API an, ermöglicht Ihnen jedoch die Verwendung benutzerdefinierter Themen, wenn Sie ein Gerät verwenden, das nativ auf einem bestimmten Thema veröffentlicht.",
"mqttCustomTopicPlaceholder": "Geben Sie das MQTT-Thema ein",
"copyMqttTopic": "MQTT-Thema kopieren",
"mqttCustomObjectPath": "MQTT JSON Nachricht",
"mqttCustomObjectPathDescription": "Wenn Ihr verbundenes Objekt MQTT-Nachrichten in Form eines JSON sendet, können Sie hier den Pfad der Eigenschaft angeben, der von Gladys verwendet werden soll. Beispiel: Eigenschaft1.Eigenschaft2.Eigenschaft3. Lassen Sie es leer, wenn das Format kein JSON ist.",
"mqttCustomObjectPathPlaceholder": "Pfad im JSON folgen",
"copied": "Kopiert!",
"copyFailed": "Kopieren fehlgeschlagen",
"deleteLabel": "Funktion löschen"
Expand Down Expand Up @@ -1264,6 +1271,15 @@
"connectionError": "Die Verbindung ist fehlgeschlagen. Bitte überprüfe deine Konfiguration.",
"networkError": "Gladys kann nicht erreicht werden. Ist deine Gladys-Instanz verbunden und erreichbar?",
"disconnected": "Vom MQTT-Broker getrennt."
},
"debug": {
"title": "Debug MQTT",
"description": "Dieses Register ermöglicht es Ihnen, in Echtzeit die MQTT-Nachrichten zu sehen, die auf Ihrer Gladys-Instanz eingehen, und erleichtert so das Debuggen. Nach 2 Minuten auf diesem Register wird der Debug-Modus automatisch deaktiviert. Sie können ihn mit der Aktualisierungsschaltfläche oben rechts wieder aktivieren.",
"activateDebugMode": "Aktivieren",
"debugModeActivated": "Debug-Modus aktiviert",
"date": "Zeit",
"topic": "Thema",
"message": "Nachrichteninhalt"
}
},
"broadlink": {
Expand Down
16 changes: 16 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,7 @@
"title": "MQTT",
"description": "Connect to a local or remote MQTT server",
"deviceTab": "Devices",
"debugTab": "MQTT Debug",
"setupTab": "Setup",
"documentation": "MQTT documentation",
"apiDocumentation": "MQTT API documentation ",
Expand Down Expand Up @@ -1235,7 +1236,13 @@
"mqttTopicToPublishExampleDescription": "Gladys is listening to this topic. If you publish a value in this topic, it'll be associated with this device. (see <a href=\"https://gladysassistant.com/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">doc</a>)",
"mqttTopicToListenExampleLabel": "MQTT Topic to listen",
"mqttTopicToListenExampleDescription": "Gladys will publish in this topic if the device is controlled in Gladys. You need to listen to this topic. (see <a href=\"https://gladysassistant.com/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">doc</a>)",
"mqttCustomTopic": "Custom MQTT Topic",
"mqttCustomTopicDescription": "Gladys offers a default MQTT API but allows you to use custom topics if you are using a device that publishes on a particular topic natively.",
"mqttCustomTopicPlaceholder": "Enter MQTT topic",
"copyMqttTopic": "Copy MQTT Topic",
"mqttCustomObjectPath": "MQTT JSON Message",
"mqttCustomObjectPathDescription": "If your connected object sends MQTT messages in the form of a JSON, Gladys allows you to specify here the path of the property to be used by Gladys. Example: property1.property2.property3. Leave blank if the format is not JSON.",
"mqttCustomObjectPathPlaceholder": "Path to follow in the JSON",
"copied": "Copied!",
"copyFailed": "Fail to copy",
"deleteLabel": "Delete feature"
Expand Down Expand Up @@ -1264,6 +1271,15 @@
"connectionError": "Error while connecting, please check your configuration.",
"networkError": "Unable to contact Gladys, is your Gladys instance connected and accessible?",
"disconnected": "Disconnected from MQTT broker."
},
"debug": {
"title": "Debug MQTT",
"description": "This tab allows you to see in real-time the MQTT messages arriving on your Gladys instance, enabling you to debug easily. After 2 minutes on this tab, debug mode will automatically deactivate. You can reactivate it with the refresh button at the top right.",
"activateDebugMode": "Activate",
"debugModeActivated": "Debug mode activated",
"date": "Time",
"topic": "Topic",
"message": "Message Content"
}
},
"broadlink": {
Expand Down
16 changes: 16 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,7 @@
"title": "MQTT",
"description": "Connexion à un serveur MQTT, en local ou en distant.",
"deviceTab": "Appareils",
"debugTab": "Debug MQTT",
"setupTab": "Configuration",
"documentation": "Documentation MQTT",
"apiDocumentation": "Documentation MQTT API",
Expand Down Expand Up @@ -1363,7 +1364,13 @@
"mqttTopicToPublishExampleDescription": "Gladys écoute ce topic. Si vous publiez une valeur dedans, Gladys l'associera à cette fonctionnalité. ( voir <a href=\"https://gladysassistant.com/fr/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">doc</a> )",
"mqttTopicToListenExampleLabel": "Topic MQTT à écouter",
"mqttTopicToListenExampleDescription": "Gladys publiera un message dans ce topic si cet appareil est contrôlé depuis Gladys. Vous devez écouter ce topic. ( voir <a href=\"https://gladysassistant.com/fr/docs/api/mqtt-api/\" target=\"_blank\" rel=\"noopener noreferrer\">doc</a> )",
"mqttCustomTopic": "Topic MQTT personnalisé",
"mqttCustomTopicDescription": "Gladys propose une API MQTT par défaut, mais vous permet d'utiliser des topics personnalisés si vous utilisez un appareil qui publie nativement sur un topic particulier.",
"mqttCustomTopicPlaceholder": "Entrez un topic MQTT",
"copyMqttTopic": "Copier le topic MQTT",
"mqttCustomObjectPath": "Message MQTT JSON",
"mqttCustomObjectPathDescription": "Si votre objet connecté envoie des messages MQTT sous forme d'un JSON, Gladys vous permet de spécifier ici le chemin de la propriété à utiliser par Gladys. Exemple: property1.property2.property3. Laissez vide si le format n'est pas du JSON.",
"mqttCustomObjectPathPlaceholder": "Chemin à suivre dans le JSON",
"copied": "Copié !",
"copyFailed": "Erreur lors de la copie",
"deleteLabel": "Supprimer la fonctionnalité"
Expand Down Expand Up @@ -1392,6 +1399,15 @@
"connectionError": "Erreur lors de la connexion, veuillez vérifier votre configuration.",
"networkError": "Impossible de contacter Gladys, est-ce que votre instance Gladys est bien connectée et accessible ?",
"disconnected": "Déconnecté du broker MQTT."
},
"debug": {
"title": "Debug MQTT",
"description": "Cet onglet vous permet de voir en temps réel les messages MQTT qui arrivent sur votre instance Gladys, ce qui vous permet de débugger simplement. Au bout de 2 minutes sur cet onglet, le mode débug se désactivera automatiquement. Vous pouvez le réactiver avec le bouton rafraichir en haut à droite.",
"activateDebugMode": "Activer",
"debugModeActivated": "Mode debug activé",
"date": "Heure",
"topic": "Topic",
"message": "Contenu du message"
}
},
"broadlink": {
Expand Down
11 changes: 11 additions & 0 deletions front/src/routes/integration/all/mqtt/MqttPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ const MqttPage = ({ children, user }) => (
<Text id="integration.mqtt.deviceTab" />
</Link>

<Link
href="/dashboard/integration/device/mqtt/debug"
activeClassName="active"
class="list-group-item list-group-item-action d-flex align-items-center"
>
<span class="icon mr-3">
<i class="fe fe-code" />
</span>
<Text id="integration.mqtt.debugTab" />
</Link>

<Link
href="/dashboard/integration/device/mqtt/setup"
activeClassName="active"
Expand Down
119 changes: 119 additions & 0 deletions front/src/routes/integration/all/mqtt/debug-page/Debug.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Component } from 'preact';
import { connect } from 'unistore/preact';
import { Text } from 'preact-i18n';
import MqttPage from '../MqttPage';
import update from 'immutability-helper';
import dayjs from 'dayjs';
import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../../../server/utils/constants';

class MqttNodePage extends Component {
state = {
messages: []
};
setDebugMode = async () => {
try {
await this.props.httpClient.post('/api/v1/service/mqtt/debug_mode', {
debug_mode: true
});
this.setState({
debugModeActivated: true
});
clearTimeout(this.disableDebugModeTimeout);
this.disableDebugModeTimeout = setTimeout(() => {
this.setState({
debugModeActivated: false
});
}, 120 * 1000);
} catch (e) {
console.error(e);
}
};

displayNewMqttMessage = payload => {
const now = dayjs()
.locale(this.props.user.language)
.format('HH:mm:ss');
const message = { ...payload, date: now };
const newMessages = update(this.state.messages, {
$unshift: [message]
});
this.setState({ messages: newMessages });
};

componentWillMount() {
this.setDebugMode();
this.props.session.dispatcher.addListener(
WEBSOCKET_MESSAGE_TYPES.MQTT.DEBUG_NEW_MQTT_MESSAGE,
this.displayNewMqttMessage
);
}

componentWillUnmount() {
this.props.session.dispatcher.removeListener(
WEBSOCKET_MESSAGE_TYPES.MQTT.DEBUG_NEW_MQTT_MESSAGE,
this.displayNewMqttMessage
);
}

render(props, { messages, debugModeActivated }) {
return (
<MqttPage user={props.user}>
<div class="card">
<div class="card-header">
<h1 class="card-title">
<Text id="integration.mqtt.debug.title" />
</h1>
<div class="page-options d-flex">
<button class="btn btn-outline-secondary" onClick={this.setDebugMode}>
<span class="mr-2">
<Text id="integration.mqtt.debug.activateDebugMode" />
</span>
<i class="fe fe-refresh-cw" />
</button>
</div>
</div>
<div class="card-body">
{debugModeActivated && (
<div class="alert alert-success">
<Text id="integration.mqtt.debug.debugModeActivated" />
</div>
)}
<p>
<Text id="integration.mqtt.debug.description" />
</p>
</div>
<div class="table-responsive">
<table class="table table-hover table-outline table-vcenter card-table">
<thead>
<tr>
<th>
<Text id="integration.mqtt.debug.date" />
</th>
<th>
<Text id="integration.mqtt.debug.topic" />
</th>
<th>
<Text id="integration.mqtt.debug.message" />
</th>
</tr>
</thead>
<tbody>
{messages.map(message => (
<tr>
<td>{message.date}</td>
<td>{message.topic}</td>
<td>
<pre>{message.message}</pre>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</MqttPage>
);
}
}

export default connect('user,session,httpClient', {})(MqttNodePage);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DEVICE_FEATURE_CATEGORIES
} from '../../../../../../../../server/utils/constants';
import { DeviceFeatureCategoriesIcon, RequestStatus } from '../../../../../../utils/consts';
import { getDeviceParam } from '../../../../../../utils/device';
import get from 'get-value';

const MqttFeatureBox = ({ children, feature, featureIndex, ...props }) => {
Expand Down Expand Up @@ -168,6 +169,46 @@ const MqttFeatureBox = ({ children, feature, featureIndex, ...props }) => {
</pre>
</div>

<div class="form-group">
<label class="form-label">
<Text id="integration.mqtt.feature.mqttCustomTopic" />
</label>
<p>
<small>
<MarkupText id="integration.mqtt.feature.mqttCustomTopicDescription" />
</small>
</p>
<Localizer>
<input
type="text"
value={props.mqttCustomTopic}
onInput={props.updateMqttCustomTopic}
class="form-control"
placeholder={<Text id="integration.mqtt.feature.mqttCustomTopicPlaceholder" />}
/>
</Localizer>
</div>

<div class="form-group">
<label class="form-label">
<Text id="integration.mqtt.feature.mqttCustomObjectPath" />
</label>
<p>
<small>
<MarkupText id="integration.mqtt.feature.mqttCustomObjectPathDescription" />
</small>
</p>
<Localizer>
<input
type="text"
value={props.mqttCustomObjectPath}
onInput={props.updateMqttCustomObjectPath}
class="form-control"
placeholder={<Text id="integration.mqtt.feature.mqttCustomObjectPathPlaceholder" />}
/>
</Localizer>
</div>

{feature.read_only === false && (
<div class="form-group">
<label class="form-label">
Expand Down Expand Up @@ -227,6 +268,24 @@ class MqttFeatureBoxComponent extends Component {
};
this.props.updateFeatureProperty(e, 'keep_history', this.props.featureIndex);
};
getCustomMqttTopicParamPrefix = () => {
return `mqtt_custom_topic_feature:${this.props.feature.id}`;
};
getCustomMqttObjectPathParamPrefix = () => {
return `mqtt_custom_object_path_feature:${this.props.feature.id}`;
};
getCustomMqttTopicValue = () => {
return getDeviceParam(this.props.device, this.getCustomMqttTopicParamPrefix());
};
getCustomMqttObjectPathValue = () => {
return getDeviceParam(this.props.device, this.getCustomMqttObjectPathParamPrefix());
};
updateMqttCustomTopic = e => {
this.props.updateDeviceParam(this.getCustomMqttTopicParamPrefix(), e.target.value);
};
updateMqttCustomObjectPath = e => {
this.props.updateDeviceParam(this.getCustomMqttObjectPathParamPrefix(), e.target.value);
};
deleteFeature = () => {
this.props.deleteFeature(this.props.featureIndex);
};
Expand Down Expand Up @@ -256,6 +315,8 @@ class MqttFeatureBoxComponent extends Component {
};
render() {
const { publishMqttTopic, listenMqttTopic } = this.getMqttTopic();
const mqttCustomTopic = this.getCustomMqttTopicValue();
const mqttCustomObjectPath = this.getCustomMqttObjectPathValue();
return (
<MqttFeatureBox
{...this.props}
Expand All @@ -271,6 +332,10 @@ class MqttFeatureBoxComponent extends Component {
copyMqttTopic={this.copyMqttTopic}
publishMqttTopic={publishMqttTopic}
listenMqttTopic={listenMqttTopic}
mqttCustomTopic={mqttCustomTopic}
mqttCustomObjectPath={mqttCustomObjectPath}
updateMqttCustomTopic={this.updateMqttCustomTopic}
updateMqttCustomObjectPath={this.updateMqttCustomObjectPath}
/>
);
}
Expand Down
Loading

0 comments on commit 88e10e2

Please sign in to comment.