Accueil / Domotique / MySensors v2 : décoder/envoyer des messages avec Node-RED

MySensors v2 : décoder/envoyer des messages avec Node-RED

Les objets MySensors communiquent entre eux sous la forme de petits messages texte envoyés par ondes radio. Il est très facile de décoder les messages en suivants les spécifications techniques de l’API série disponible sur le site officiel du projet. La librairie MySensors permet de faciliter le développement d’applications en tout genre (mobile, robotique, domotique) sans avoir à gérer la couche de communication entre les appareils. Nous allons utiliser Node-RED pour décoder les messages reçus par une gateway réseau ou série.

Pour en savoir plus sur la librairie MySensors, vous pouvez aussi lire cet article

Structure d’un message MySensors

MySensors communique avec de petits messages de 32 bytes (la taille maximale des messages envoyés par une puce nRF24L01). L’entête (header) du message est de 7 bytes. Il est composé ainsi :

node-id ; child-sensor-id ; message-type ; ack ; sub-type ; payload \n

  • node-id : le numéro du noeud.
  • child-sensor-id : l’identifiant d’un enfant. Par exemple la température mesurée par un capteur DHT22.
  • message-type : type de message
    • 0. Présentation du noeud. Il est envoyé lors de l’exécution de la fonction presentation() au démarrage du noeud
    • 1. Set. Envoi de données par un capteur
    • 2. Req. Demande à un actionneur d’executer la commande demandée
    • 3. Internal. Message interne
    • 4. Stream. Utilisé pour la mise à jour sans fil (OTA)
  • ack : demande d’accusé réception (ou accusé reception)
  • sub-type : le sous-type dépend du message-type
  • payload : contenu du message. Il est limité à 25 bytes.

25 bytes peut sembler faible, mais le nRF24L01 n’est pas conçu pour transmettre de grands messages, plutôt des états ou des mesures physiques.

Se connecter à une gateway MySensors avec Node-RED

On dispose de deux types de gateway MySensors. La serial gateway qui se branche sur le port USB d’un ordinateur ou d’un Raspberry. Elle est pratique pour débuter et lire les messages de debuggage envoyés sur le port série (par exemple avec le moniteur série de l’IDE Arduino). La passerelle réseau est maintenant très simple (et pas cher) à fabriquer à l’aide d’un ESP8266 (toutes les explications dans cet article). On peut l’installer (la cacher !) dans en endroit ou la réception radio est meilleure et on ne monopolise pas un port USB du Raspberry Pi.

Serial gateway, passerelle série sur port USB

Pour se connecter à une passerelle série, ajoutez un Node serial (dans Input)

Node-red serial node

Ouvrez le pour le configurer et appuyez sur le crayon pour configurer le port série. Appuyez sur la loupe et choisissez le port USB sur lequel est branché la gateway. Par exemple /dev/ttyUSB0.

node-red serial liste gateway usb

 

node-red serial ok

Par défaut, la vitesse de communication avec la gateway est de 115200 bauds. Laissez la configuration par défaut. 8 bits Data, Parity None, Stop Bits 1.

node-red serial settings

Enregistrez la configuration en appuyant sur Done.

LAN Gateway, passerelle réseau

Pour se connecter à une passerelle réseau, nous allons utiliser un Node TCP

node-red tcp mysensors lan gateway

Configurez la connexion ainsi :

  • Type : Connect to
  • Port : 5003 par défaut (à adapter à votre configuration)
  • at host : l’adresse ip de la gateway réseau
  • output : stream of … String
  • Donnez un nom
  • Enregistrez avec Done

node-red tcp mysensors lan gateway settings parametres

Plugin MySensors pour Node-RED

Le plugin node-red-contrib-mysensors développé par Thomas Mørch permet de décoder les messages MySensors et d’en envoyer à un noeud. Il s’installe avec cette commande

npm install node-red-contrib-mysensors

Après installation, relancez Node-RED avec les commandes suivantes et actualisez le navigateur.

node-red-stop
node-red-start

Vous disposer maintenant d’une nouvelle palette appelée mysensors. Elle est composée de trois Nodes

  • mysdecenc : encodeur / décodeur de messages MySensors
  • mysencap : prépare le message à envoyer au noeud
  • mysdebug : rend lisible de messages MySensors

Fonction mysdebug

Partons par la fin pour une fois. La fonction mysdebug permet de décoder et de rendre plus lisible les messages reçus en envoyés par la gateway. Chaque code est remplacé par sa signification.

node-red mysensors mysdebug

Messages décodés affichés dans la console. Tout n’est pas correctement décodé (dans la version actuelle du plugin).

node-red mysensors mysdecenc mysdebug console log

Comment utiliser la fonction mysdecenc

Cette fonction permet de décoder les messages MySensors. pour l’utiliser, il suffit de la brancher à une gateway (série ou réseau). En sortie (payload), on récupère directement le payload de chaque message. Chaque information disponible est publiée sur un flux différent :

  • msg.payload : contenu du message (payload) envoyé par le noeud
  • msg.nodeId : noeud d’origine du message
  • msg.childSensorId : Id de l’enfant attaché au noeud
  • msg.messageType : type de message
  • msg.ack : demande d’accusé réception
  • msg.subType : sous-type de message

Cette architecture présente des avantages et des inconvénients. On récupère directement en sortie le payload de chaque message mais cela peut rapidement devenir compliqué pour filtrer les données de tel ou tel capteur.

node-red mysensors mysdecenc mysdebug

Messages décodés affichés dans la console.

node-red mysensors mysdecenc mysdebug console log

Code du flow

[{"id":"62351421.a2a7ec","type":"serial in","z":"eb4ead14.fd77f","name":"MySensors Serial Gateway","serial":"219cc581.f9b252","x":145.6666717529297,"y":134.66666412353516,"wires":[["2027984.714eb68","d8174c63.f752a"]]},{"id":"893c2411.1181e8","type":"debug","z":"eb4ead14.fd77f","name":"","active":false,"console":"false","complete":"payload","x":559,"y":35,"wires":[]},{"id":"d8174c63.f752a","type":"mysdecenc","z":"eb4ead14.fd77f","name":"","x":372.5,"y":36,"wires":[["893c2411.1181e8","80842fe1.74a72"]]},{"id":"80842fe1.74a72","type":"debug","z":"eb4ead14.fd77f","name":"","active":false,"console":"false","complete":"nodeId","x":559.5,"y":80,"wires":[]},{"id":"2027984.714eb68","type":"mysdebug","z":"eb4ead14.fd77f","name":"","x":374.5,"y":135,"wires":[["ed9596a0.eaa2b8"]]},{"id":"ed9596a0.eaa2b8","type":"debug","z":"eb4ead14.fd77f","name":"","active":false,"console":"false","complete":"false","x":561.5,"y":140,"wires":[]},{"id":"219cc581.f9b252","type":"serial-port","z":"","serialport":"/dev/ttyUSB0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","newline":"\\n","bin":"false","out":"char","addchar":false}]

Fonction de décodage renvoyant un objet JSON

Je trouve plus facile et plus explicite de manipuler un objet JSON. J’ai développé une petite fonction que vous pouvez ajouter à vos projets Node-RED. Elle décode chaque message et créé un objet JSON au format clé:valeur.

node-red mysensors decoder projetsdiy

Elle renvoie un payload avec les informations suivantes :

  • nodeId : Id du noeud publiant le message
  • sensorId : Id de l’enfant
  • mode : presentation, set, req, internal, stream
  • type : numéro du sous-type de donnée (fonction du mode)
  • typeLabel : libellé du type de données correspondant
  • value : contenu du message (payload)

Vous pouvez également installer ce flow depuis le site officiel de Node-RED http://flows.nodered.org/flow/fa02078c160cb3e00e09f4980b534490

/* MySensors v2 Message Decoder
*  Payload : JSON object
*  www.projetsdiy.fr - oct. 2016
*/
var mySensorsMessage = {}
var newPayload = {};
var message = msg.payload.toString();
message = message.replace(/(\r\n|\n|\r)/gm, "");
var tokens = message.split(";")
if(tokens.length == 6)
{
    mySensorsMessage.nodeId =       parseInt(tokens[0]);
    mySensorsMessage.childSensorId= parseInt(tokens[1]);
    mySensorsMessage.messageType =  parseInt(tokens[2]);
    mySensorsMessage.ack =          parseInt(tokens[3]);
    mySensorsMessage.subType =      parseInt(tokens[4]);
    mySensorsMessage.value =        Number(tokens[5]);

    var messageType = mySensorsMessage.messageType;
    var subType = mySensorsMessage.subType;
    var labelPresentation = ["S_DOOR","S_MOTION","S_SMOKE","S_LIGHT","S_BINARY","S_DIMMER","S_COVER","S_TEMP","S_HUM","S_BARO","S_WIND","S_RAIN","S_UV","S_WEIGHT","S_POWER","S_HEATER","S_DISTANCE","S_LIGHT_LEVEL","S_ARDUINO_NODE","S_ARDUINO_REPEATER_NODE","S_LOCK","S_IR","S_WATER","S_AIR_QUALITY","S_CUSTOM","S_DUST","S_SCENE_CONTROLLER","S_RGB_LIGHT","S_RGBW_LIGHT","S_COLOR_SENSOR","S_HVAC","S_MULTIMETER","S_SPRINKLER","S_WATER_LEAK","S_SOUND","S_VIBRATION","S_MOISTURE","S_INFO","S_GAS","S_GPS","S_WATER_QUALITY"];
    var labelSet = ["V_TEMP","V_HUM","V_STATUS","V_LIGHT","V_PERCENTAGE","V_DIMMER","V_PRESSURE","V_FORECAST","V_RAIN","V_RAINRATE","V_WIND","V_GUST","V_DIRECTION","V_UV","V_WEIGHT","V_DISTANCE","V_IMPEDANCE","V_ARMED","V_TRIPPED","V_WATT","V_KWH","V_SCENE_ON","V_SCENE_OFF","V_HVAC_FLOW_STATE","V_HVAC_SPEED","V_LIGHT_LEVEL","V_VAR1","V_VAR2","V_VAR3","V_VAR4","V_VAR5","V_UP","V_DOWN","V_STOP","V_IR_SEND","V_IR_RECEIVE","V_FLOW","V_VOLUME","V_LOCK_STATUS","V_LEVEL","V_VOLTAGE","V_CURRENT","V_RGB","V_RGBW","V_ID","V_UNIT_PREFIX","V_HVAC_SETPOINT_COOL","V_HVAC_SETPOINT_HEAT","V_HVAC_FLOW_MODE","V_TEXT","V_CUSTOM","V_POSITION","V_IR_RECORD","V_PH","V_ORP","V_EC","V_VAR","V_VA","V_POWER_FACTOR"]
    var labelInternal = ["I_BATTERY_LEVEL","I_TIME","I_VERSION","I_ID_REQUEST","I_ID_RESPONSE","I_INCLUSION_MODE","I_CONFIG","I_FIND_PARENT","I_FIND_PARENT_RESPONSE","I_LOG_MESSAGE","I_CHILDREN","I_SKETCH_NAME","I_SKETCH_VERSION","I_REBOOT","I_GATEWAY_READY","I_REQUEST_SIGNING","I_GET_NONCE","I_GET_NONCE_RESPONSE","I_HEARTBEAT","I_PRESENTATION","I_DISCOVER","I_DISCOVER_RESPONSE","I_HEARTBEAT_RESPONSE","I_LOCKED","I_PING","I_PONG","I_REGISTRATION_REQUEST","I_REGISTRATION_RESPONSE","I_DEBUG"]
    
    switch (messageType) {
        case 0:     // Presentation
            
            newPayload.mode =       "Presentation";
            newPayload.type =       labelPresentation[subType];
            break;
        case 1:     // Set
            newPayload.nodeId=      mySensorsMessage.nodeId;
            newPayload.sensorId=    mySensorsMessage.childSensorId;
            newPayload.mode=        "Set";
            newPayload.type=        subType;
            newPayload.typeLabel=   labelSet[subType];
            newPayload.value=       mySensorsMessage.value;
            break;
        case 2:     // Req
            newPayload.nodeId=      mySensorsMessage.nodeId;
            newPayload.sensorId=    mySensorsMessage.childSensorId;
            newPayload.mode=        "Req";
            newPayload.type=        subType;
            newPayload.typeLabel=   labelSet[subType];
            newPayload.value=       mySensorsMessage.value;
            break;  
        case 3:     // Internal
            newPayload.nodeId=      mySensorsMessage.nodeId;
            newPayload.sensorId=    mySensorsMessage.childSensorId;
            newPayload.mode=        "Internal";
            newPayload.type=        subType;
            newPayload.typeLabel=   labelInternal[subType];
            newPayload.value=       mySensorsMessage.value;
            break;    
        case 4:     // Stream - OTA firmware update
            newPayload.nodeId=      mySensorsMessage.nodeId;
            newPayload.mode=        "stream";
            break;
        default:
            break;
    }

    msg.payload = newPayload; 
} else {
    msg.payload = "Error! Nothing to decode"
}  

return msg;

Voici ce que vous obtiendrez avec la fonction. Chaque message est maintenant explicite et formaté sous la forme d’un objet JSON plus facile à exploiter dans un projet.
node-red mysensors decoder projetsdiy console log

Libre à vous de la modifier en fonction de vos besoins.

Code du flow

[{"id":"61bbd468.bddf9c","type":"tcp in","z":"eb4ead14.fd77f","name":"MySensors Gateway","server":"client","host":"192.168.1.20","port":"5003","datamode":"stream","datatype":"utf8","newline":"","topic":"","base64":false,"x":169.88886260986328,"y":226.92767333984375,"wires":[["f4fd4940.32ba8"]]},{"id":"f4fd4940.32ba8","type":"function","z":"eb4ead14.fd77f","name":"Decode MySensor Message","func":"/* MySensors v2 Message Decoder\n*  Payload : JSON object\n*  www.projetsdiy.fr - oct. 2016\n*/\nvar mySensorsMessage = {}\nvar newPayload = {};\nvar message = msg.payload.toString();\nmessage = message.replace(/(\\r\\n|\\n|\\r)/gm, \"\");\nvar tokens = message.split(\";\")\nif(tokens.length == 6)\n{\n    mySensorsMessage.nodeId =       parseInt(tokens[0]);\n    mySensorsMessage.childSensorId= parseInt(tokens[1]);\n    mySensorsMessage.messageType =  parseInt(tokens[2]);\n    mySensorsMessage.ack =          parseInt(tokens[3]);\n    mySensorsMessage.subType =      parseInt(tokens[4]);\n    mySensorsMessage.value =        Number(tokens[5]);\n\n    var messageType = mySensorsMessage.messageType;\n    var subType = mySensorsMessage.subType;\n    var labelPresentation = [\"S_DOOR\",\"S_MOTION\",\"S_SMOKE\",\"S_LIGHT\",\"S_BINARY\",\"S_DIMMER\",\"S_COVER\",\"S_TEMP\",\"S_HUM\",\"S_BARO\",\"S_WIND\",\"S_RAIN\",\"S_UV\",\"S_WEIGHT\",\"S_POWER\",\"S_HEATER\",\"S_DISTANCE\",\"S_LIGHT_LEVEL\",\"S_ARDUINO_NODE\",\"S_ARDUINO_REPEATER_NODE\",\"S_LOCK\",\"S_IR\",\"S_WATER\",\"S_AIR_QUALITY\",\"S_CUSTOM\",\"S_DUST\",\"S_SCENE_CONTROLLER\",\"S_RGB_LIGHT\",\"S_RGBW_LIGHT\",\"S_COLOR_SENSOR\",\"S_HVAC\",\"S_MULTIMETER\",\"S_SPRINKLER\",\"S_WATER_LEAK\",\"S_SOUND\",\"S_VIBRATION\",\"S_MOISTURE\",\"S_INFO\",\"S_GAS\",\"S_GPS\",\"S_WATER_QUALITY\"];\n    var labelSet = [\"V_TEMP\",\"V_HUM\",\"V_STATUS\",\"V_LIGHT\",\"V_PERCENTAGE\",\"V_DIMMER\",\"V_PRESSURE\",\"V_FORECAST\",\"V_RAIN\",\"V_RAINRATE\",\"V_WIND\",\"V_GUST\",\"V_DIRECTION\",\"V_UV\",\"V_WEIGHT\",\"V_DISTANCE\",\"V_IMPEDANCE\",\"V_ARMED\",\"V_TRIPPED\",\"V_WATT\",\"V_KWH\",\"V_SCENE_ON\",\"V_SCENE_OFF\",\"V_HVAC_FLOW_STATE\",\"V_HVAC_SPEED\",\"V_LIGHT_LEVEL\",\"V_VAR1\",\"V_VAR2\",\"V_VAR3\",\"V_VAR4\",\"V_VAR5\",\"V_UP\",\"V_DOWN\",\"V_STOP\",\"V_IR_SEND\",\"V_IR_RECEIVE\",\"V_FLOW\",\"V_VOLUME\",\"V_LOCK_STATUS\",\"V_LEVEL\",\"V_VOLTAGE\",\"V_CURRENT\",\"V_RGB\",\"V_RGBW\",\"V_ID\",\"V_UNIT_PREFIX\",\"V_HVAC_SETPOINT_COOL\",\"V_HVAC_SETPOINT_HEAT\",\"V_HVAC_FLOW_MODE\",\"V_TEXT\",\"V_CUSTOM\",\"V_POSITION\",\"V_IR_RECORD\",\"V_PH\",\"V_ORP\",\"V_EC\",\"V_VAR\",\"V_VA\",\"V_POWER_FACTOR\"]\n    var labelInternal = [\"I_BATTERY_LEVEL\",\"I_TIME\",\"I_VERSION\",\"I_ID_REQUEST\",\"I_ID_RESPONSE\",\"I_INCLUSION_MODE\",\"I_CONFIG\",\"I_FIND_PARENT\",\"I_FIND_PARENT_RESPONSE\",\"I_LOG_MESSAGE\",\"I_CHILDREN\",\"I_SKETCH_NAME\",\"I_SKETCH_VERSION\",\"I_REBOOT\",\"I_GATEWAY_READY\",\"I_REQUEST_SIGNING\",\"I_GET_NONCE\",\"I_GET_NONCE_RESPONSE\",\"I_HEARTBEAT\",\"I_PRESENTATION\",\"I_DISCOVER\",\"I_DISCOVER_RESPONSE\",\"I_HEARTBEAT_RESPONSE\",\"I_LOCKED\",\"I_PING\",\"I_PONG\",\"I_REGISTRATION_REQUEST\",\"I_REGISTRATION_RESPONSE\",\"I_DEBUG\"]\n    \n    switch (messageType) {\n        case 0:     // Presentation\n            \n            newPayload.mode =       \"Presentation\";\n            newPayload.type =       labelPresentation[subType];\n            break;\n        case 1:     // Set\n            newPayload.nodeId=      mySensorsMessage.nodeId;\n            newPayload.sensorId=    mySensorsMessage.childSensorId;\n            newPayload.mode=        \"Set\";\n            newPayload.type=        subType;\n            newPayload.typeLabel=   labelSet[subType];\n            newPayload.value=       mySensorsMessage.value;\n            break;\n        case 2:     // Req\n            newPayload.nodeId=      mySensorsMessage.nodeId;\n            newPayload.sensorId=    mySensorsMessage.childSensorId;\n            newPayload.mode=        \"Req\";\n            newPayload.type=        subType;\n            newPayload.typeLabel=   labelSet[subType];\n            newPayload.value=       mySensorsMessage.value;\n            break;  \n        case 3:     // Internal\n            newPayload.nodeId=      mySensorsMessage.nodeId;\n            newPayload.sensorId=    mySensorsMessage.childSensorId;\n            newPayload.mode=        \"Internal\";\n            newPayload.type=        subType;\n            newPayload.typeLabel=   labelInternal[subType];\n            newPayload.value=       mySensorsMessage.value;\n            break;    \n        case 4:     // Stream - OTA firmware update\n            newPayload.nodeId=      mySensorsMessage.nodeId;\n            newPayload.mode=        \"stream\";\n            break;\n        default:\n            break;\n    }\n\n    msg.payload = newPayload; \n} else {\n    msg.payload = \"Error! Nothing to decode\"\n}  \n\nreturn msg;","outputs":1,"noerr":0,"x":394.5555419921875,"y":279.5555725097656,"wires":[["22cb3401.ef7e24","2d12c5a2.7304aa","fd30fa09.244ce8"]]},{"id":"fd30fa09.244ce8","type":"debug","z":"eb4ead14.fd77f","name":"","active":true,"console":"false","complete":"false","x":628.5,"y":235,"wires":[]}]

La fonction mysencap

La dernière fonction mysencap proposée par le plugin permet de formater un message avant de le publier sur un noeud du réseau MySensors. On peut envoyer ce que l’on veut dans la limite de 25 bytes. Il faut également ajouter un retour à la ligne (\n) avant de l’injecter dans la fonction. Par exemple, on peut récupérer l’état d’un switch créé avec le plugin node-red-contrib-ui pour allumer ou éteindre une Led (ou une lampe via un relai). Nous verrons en détail ce plugin dans un prochain tutoriel.

Ajoutez un Node swich

http://www.projetsdiy.fr/wp-content/uploads/2016/10/17.-node-red-contrib-ui switch

Donnez lui un nom. Pour la valeur de sortie, indiquez 1 et 0 pour Off. Ce n’est pas très important, on peut très bien laisser True/False. Il faudra juste tester la bonne valeur dans le code Arduino du noeud MySensors.

configuration switch node-red-contrib-ui

Allez sur la page d’interface à l’adresse

http://IP_NODE-RED:1880/ui

Vous disposer maintenant d’un interrupteur à deux états.

node-red-contrib-ui switch mysensors

Il ne reste plus qu’à préparer le message. Ajoutez une fonction et coller ce code pour ajouter un retour à la ligne.

msg.payload = msg.payload + "\n"
return msg;

Ensuite ajoutez un Node mysencap et indiquez les paramètres du noeud de destination du message. Le type de message doit être Request. Voici un exemple :

configuration mysencap node-red mysensors.

Il ne reste plus qu’à injecter dans un Node mysdecenc avant d’envoyer le message sur une gateway (série ou réseau) MySensors.

piloter relai led mysensors depuis node-red

Ajoutez ce code sur le noeud MySensor pour intercepter les messages envoyés à cet objet.

void receive(const MyMessage &message) {
     Serial.print("Message recu pour le capteur :");
     Serial.print(message.sensor);
     Serial.print(", Nouveau statut : ");
     Serial.println(message.getBool()); 
}

Et voilà, si on appuie sur l’interrupteur, on reçoit bien les messages sur le noeud en provenance du flow Node-RED.

node-red mysensors iot reception message receive

Code du flow

[{"id":"f34523e0.fa94a","type":"mysdecenc","z":"e1e0f04d.8c404","name":"","x":546.388916015625,"y":276,"wires":[["a9d71461.a61de8"]]},{"id":"45e9c56a.b8cdec","type":"mysencap","z":"e1e0f04d.8c404","name":"","nodeid":"3","childid":"0","subtype":"2","internal":0,"ack":false,"msgtype":"2","presentation":false,"presentationtype":0,"presentationtext":"","fullpresentation":false,"firmwarename":"","firmwareversion":"","x":400,"y":220.3333282470703,"wires":[["f34523e0.fa94a"]]},{"id":"a9d71461.a61de8","type":"tcp out","z":"e1e0f04d.8c404","host":"192.168.1.20","port":"5003","beserver":"client","base64":false,"end":false,"name":"to MySensors","x":738,"y":232.3333282470703,"wires":[]},{"id":"d253489c.087998","type":"function","z":"e1e0f04d.8c404","name":"slash n","func":"msg.payload = msg.payload + \"\\n\"\nreturn msg;","outputs":1,"noerr":0,"x":274,"y":276.3333282470703,"wires":[["45e9c56a.b8cdec"]]},{"id":"af4ddcee.88eaf","type":"ui_switch","z":"e1e0f04d.8c404","tab":"6df01db6.d8538c","name":"Switch","topic":"","group":"","order":1,"onvalue":"1","offvalue":"0","x":149,"y":224.3333282470703,"wires":[["d253489c.087998"]]},{"id":"6df01db6.d8538c","type":"ui_tab","z":"","name":"Salon","icon":"store","order":"1"}]