Créer un réseau LoRa et exploiter les données transmises

Nous allons voir ici comment contruire facilement votre propre réseau local LoRaWAN.

Le matériel

Avant de commencer, il est nécessaire de s'équiper d'un ou plusieurs devices constituant les noeuds finaux (transmettant les données de capteurs) ainsi que d'une passerelle (gateway) pour la centralisation des messages. Pour plus de détails sur l'architecture LoRaWAN, vous pouvez voir ici.

Pour cet article, la plateforme qui recevra nos messages LoRa (le Network server) sera The Things Network (TTN) : gratuit, complet et simple d'utilisation.

A ce jour, le matériel le plus abordable (tant en prix qu'en complexité) sur le marché est fourni par Pycom : en premier lieu leur toute nouvelle passerelle Pygate, qui est très simple à prendre en main et peu chère. Elle est programmable en MicroPython et dispose d'une extension Ethernet/PoE. Pour ce qui est des noeuds et capteurs, un LoPy4 posé sur une carte d'extensions PySense répondra à la plupart des besoins.

Je passe sur les détails de l'assemblage des cartes, très bien expliqué sur leur site.

La programmation

Le développement en MicroPython et la programmation des cartes peut se faire assez simplement avec VSCode et l'extension Pymakr.

La passerelle

Avant même de commencer à programmer la Pygate, il est nécessaire de l'enregistrer auprès de la plateforme TTN afin de récupérer des indentifiants. Pour cela, il suffit de suivre les instructions ici.

Ensuite, sur le site de Pycom se trouve un très bon exemple de code permettant de programmer la passerelle pour se connecter à TTN via Wifi. Si vous êtes équipés du module Ethernet/PoE, il suffit de remplacer le code intialisant l'interface Wifi par celui-ci :

from network import ETH

eth = ETH()
while not eth.isconnected():
    print("connecting to ethernet")
    time.sleep(1)
print("ifconfig", eth.ifconfig())

Ainsi, une fois la gateway connectée à un réseau accédant à Internet, la communication s'établira toute seule et vous verrez remonter régulièrement des signaux de vie sur la page de TTN.

Les noeuds

Pour ce qui est du code nécessaire à l'activation d'un end device, il existe encore une fois un très bon exemple sur le site de Pycom. Comme expliqué, il vous sera nécessaire de créer une application dans TTN pour récupérer tous les identifiants utiles.

Afin de transmettre les données des capteurs d'un Pysense, tout est disponible sur cette page.

Le décodage des données

Sur TTN, le payload des messages est encodé en hexadécimal, sous forme d'octets. Mais la plaforme met à disposition, au sein des Applications, des moyens de décoder et manipuler les contenus afin de faciliter leur utilisation, et ce à partir de fonctions Javascript.

Le Decoder

Ces fonctions permettent de transformer le contenu brut (octets) en données lisibles.

Prenons le scénario suivant : le payload envoyé est constitué de données encodées selon un mécanisme personnalisé : 15 premiers bits pour une température, 7 bits suivants pour une humidité... Cette succession de bits est finalement encodée en hexa afin d'être transmise.

Voici un exemple de décodeur qui transforme le payload des messages LoRa en une chaine binaire :

function Decoder(bytes, port) {
  function bytesToBinStr(bytes) {
    var binstr = '';
    for(var i=0; i<bytes.length; i++) { 
        var str = bytes[i].toString(2);
        while(str.length<8){
          str = '0'+str;
        }
        binstr += str;
    }
    return binstr;
  }
}

Ensuite, selon notre scénario, une fonction permettant de reconstituer la température :

function parseForTemperature(binStr) {
    var level = 0.0;
    if (binStr.length >= 18) { 
      var entier = parseInt(binStr.substr(3, 7), 2);
      var dec = parseInt(binStr.substr(10, 8), 2);
      level = entier + (dec / 100);
      if(binStr.substr(2, 1)=="1") {
          level = level * -1;
      }
  	}
    return level;
  }

Enfin, le code complet du Decoder permettant de retourner la température :

function Decoder(bytes, port) {

  function bytesToBinStr(bytes) {
    ...
  }
  
  function parseForTemperature(binStr) {
  	...
  }
  
  // Decode an uplink message from a buffer
  // (array) of bytes to an object of fields.
  var decoded = {};

  var binstr = bytesToBinStr(bytes);
  decoded.temp = parseForTemperature(binstr);

  return decoded;
}

Le Converter

Imaginons maintenant que nous souhaitions transmettre ces données à une plateforme tierce. Il est possible grâce aux convertisseurs de transformer les données décodées afin d'en adapter le format, pour être correctement interprétées par la plateforme cible (ceux qui me suivent reconnaitront Cumulocity ;). En voici un exemple :

function Converter(decoded, port) {
  // Merge, split or otherwise
  // mutate decoded fields.
  var date = new Date().toISOString();
  var tempMeasurement = {'c8y_TemperatureMeasurement':{'T':{'unit':'C','value':decoded.temp}}};
  tempMeasurement.type = 'c8y_TemperatureMeasurement';
  tempMeasurement.time = date;
  tempMeasurement.source = {'id':'3466704'};

  var converted = {'measurements':[tempMeasurement]};

  return converted;
}

Ce qui, ici, produira au final un message de la forme :

{
        "app_id": "pygate-test",
        "dev_id": "pysense",
        "hardware_serial": "...",
        "port": 2,
        "counter": 27,
        "payload_raw": "CBHMmQFHhVM=",
        "payload_fields": {
            "measurements": [
                {
                    "c8y_TemperatureMeasurement": {
                        "T": {
                            "unit": "C",
                            "value": 32.71
                        }
                    },
                    "source": {
                        "id": "3466704"
                    },
                    "time": "2020-10-27T16:21:50.698Z",
                    "type": "c8y_TemperatureMeasurement"
                }
            ]
        },
        "metadata": {
            "time": "2020-10-27T16:21:50.491459008Z",
            "frequency": 868.1,
            "modulation": "LORA",
            "data_rate": "SF7BW125",
            "coding_rate": "4/5",
            "gateways": [
                {
                    "gtw_id": "eui-...",
                    "timestamp": 2919800980,
                    "time": "",
                    "channel": 0,
                    "rssi": -25,
                    "snr": 10,
                    "rf_chain": 0
                }
            ]
        },
        "downlink_url": "https://integrations.thethingsnetwork.org/ttn-eu/api/v2/down/.../integromat?key=..."
    }

Vous avez maintenant tout ce qu'il vous faut pour monter votre propre réseau LoRa et faire voyager vos données!

Nous verrons dans un prochain article comment développer un microservice sur Cumulocity pour récupérer ces messages et en extraire les measurements utiles.


Fichier(s) joint(s) :