Internet of Things/Node-software

Uit Lab
Naar navigatie springen Naar zoeken springen
Internet of Things

Lessen

Installeren software

  1. NodeRed tutorial
  2. Things en nodes opdrachten

Zie ook Regels en richtlijnen
Zie ook Artikelen bewerken

Structuur van de node-software

We beschrijven hier de structuur van de node-software. Dit is een voorbeeld; er zijn alternatieve oplossingen denkbaar. Dit voorbeeld kan uitgebreid worden met andere sensoren en actuatoren. In een node moeten we de volgende taken uitvoeren:

  • bemonsteren van sensoren, met een vaste regelmaat;
  • aansturen van actuatoren;
  • communiceren met de MQTT-broker: node als MQTT-client;
  • decoderen (JSON) en interpreteren van de binnenkomende MQTT-berichten;
  • coderen (JSON) van uitgaande MQTT-berichten;
  • opzetten van IP-verbinding (voor MQTT);

Sources op GitHub

De complete sources van de node-software zijn te vinden op:

Aanpassen aan lokale situatie

De volgende onderdelen moeten aangepast worden aan de lokale situatie:

  • MAC-adres: elk Ethernet-shield heeft een eigen MAC-adres. De software moet dus voor elke node hierop aangepast worden.
  • MQTT-broker: dit kan een publieke broker zijn (domain name) of een lokale broker (meestal een IP-adres).
    • soms vereist een broker ook nog een username/password-combinatie.
  • Voor WiFi (ESP8266): naam en wachtwoord van het netwerk.

ESP8266-versie

We geven hieronder het voorbeeld van de software voor een Arduino-node. Voor een node gebaseerd op de ESP8266 (bijv. NodeMCU) moeten de volgende onderdelen aangepast worden:

  • netwerk: WiFi in plaats van Ethernet (met netwerknaam/wachtwoord)
  • sensoren, actuatoren: gebruik van andere pinnen
  • (in setup): andere snelheid van de seriële host-verbinding

De ESP8266 heeft een klein voordeel: het mac-adres kan opgevraagd worden, dit betekent dat je de software niet voor elke node afzonderlijk hoeft aan te passen.

Raspberry Pi versie

Omdat we op de Raspberry Pi Python (en een compleet O.S.) tot onze beschikking hebben, heeft de software daarvoor een iets andere opzet.

P.M.

Gebruikte externe Arduino-bibliotheken (libraries)

Voor de communicatie met de MQTT-broker gebruiken we de Arduino-bibliotheek PubSubClient (van Nick O'Leary). Informatie over deze library is te vinden via:

Voor de MQTT-berichten gebruiken we het JSON-formaat. Dit maakt het gemakkelijker om deze berichten te verwerken, in de verschillende onderdelen van de IoT-keten. Voor het coderen en decoderen van JSON-berichten gebruiken we de bibliotheek ArduinoJson (van Benoît Blanchon). Informatie over deze library is te vinden via:

Installeren van deze libraries in de Arduino IDE (eenmalig)

  • selecteer Schets->Bibliotheek gebruiken->Bibliotheken beheren
  • zoek de MQTT-bibliotheek PubSubClient (van Nick O'Leary)
  • klik op "more info"
  • installeer de library
  • zoek de JSON-bibliotheek ArduinoJson (van Benoît Blanchon)
  • klik op "more info"
  • installeer de library

Importeren van deze libraries in het Arduino-programma

Voordat we de functies en andere elementen uit deze libraries in ons Arduino-programma kunnen gebruiken, moeten we eerst deze libraries importeren. Dit doen we aan het begin van het programma, als volgt:

#include <Ethernet.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

De Ethernet-library is een Arduino-library voor het aansturen van het Ethernet-shield. Deze library maakt de internet-communicatie mogelijk die de basis is voor de MQTT-communicatie.

Communicatie met MQTT-broker

Internet-verbinding

MQTT maakt gebruik van de internet-verbinding, in dit geval via het Arduino Ethernet-shield:

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x4D, 0x60};

EthernetClient ethClient;
  • het MAC-adres van het Ethernet-shield moet je voor elk shield afzonderlijk invullen. Je kunt dan niet hetzelfde programma op verschillende nodes laten werken. We proberen de code zo op te zetten dat je dit maar op één plaats hoeft aan te passen.
  • voor de ESP8266-systemen kun je het MAC-adres opvragen, dan kun je wel eenzelfde programma op verschillende nodes uitvoeren.
  • de internet-communicatie verloopt via ethClient.

De internet-verbinding zetten we als volgt op:

void networkSetup() {
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    for (;;)
      ;
  }
  // print your local IP address:
  Serial.print("My IP address: ");
  Serial.println(Ethernet.localIP());
}

Node-identificatie

Voor de identificatie van een node in MQTT gebruiken we de laatste 2 bytes van het MAC-adres.

String nodeID;

void setup() {
  //...
  networkSetup();
  nodeID = String(mac[4] * 256 + mac[5], HEX));
  //...
}

Opzetten en instandhouden van de MQTT-communicatie

const char* mqttServer = "mqttbroker";
// alternative: IPAddress mqttServer(172, 16, 0, 2);
const int mqttPort = 1883;

PubSubClient client(ethClient);

void setup() {
  // ...
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
}

Het client-object biedt het interface voor de interactie met de MQTT-broker. We moeten dit eerst koppelen aan het internet-interface (ethClient), en daarna aan de server en de callback-functie.

De callback-functie wordt vanuit dit client-object aangeroepen als er een MQTT-bericht binnenkomt voor een topic waarop deze client geabonneerd is. We beschrijven deze functie verderop.

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    String clientID = "IoTClient-" + nodeID;
    if (client.connect(clientID.c_str())) {
      Serial.println("connected");

      client.subscribe(actuatorTopic.c_str());
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

Verzenden van MQTT-berichten

Voor het versturen van een MQTT-bericht gebruiken we de functie (method) client.publish(<topic>, <message>);. Voor een voorbeeld zie het versturen van sensor-data in JSON-formaat.

Ontvangen van MQTT berichten

Als er een MQTT-bericht ontvangen wordt, van een topic waarop deze client geabonneerd is, dan wordt de functie aangeroepen die we opgegeven hebben als callback-functie.

void mqttCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if (strcmp(topic, actuatorTopic.c_str())==0) {
    Serial.println("actuator message received");
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.parseObject((char*) payload);
    if (root.success()) {
      if (root.containsKey("led0")) {
        digitalWrite(led0, root["led0"]);
      }
    }
  }
}

JSON coderen en decoderen

We gebruiken het JSON-formaat voor de MQTT-berichten. Bij het versturen moeten we de sensorgegevens in deze vorm gieten. Bij het ontvangen moeten we het bericht interpreteren, en de juiste actie(s) ondernemen.

Voor de manier waarop je de JSON-library gebruikt, zie de documentatie:

Coderen en versturen

Als voorbeeld geven we hier het inpakken en versturen van de gegevens van sensor1: een analoge sensor (bijv. een LDR-lichtsensor). Als onderdeel van een bericht geven we ook de ID van de node mee, en de lokale tijd (in msec sinds het opstarten).

void publish_sensor1() {
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  String msg;
  root["id"] = nodeID;
  root["sensor1"] = analogRead(A0);;
  root["localtime"] = millis();
  root.printTo(msg);
  Serial.println(msg);
  client.publish(sensorTopic.c_str(), msg.c_str());
}

Ontvangen en decoderen

Bij het ontvangen moet je eerst uitzoeken welk MQTT-topic het betreft. Als het om het actuators-topic gaat, moet je het bericht verder ontleden (via de JSON-library). In het onderstaande voorbeeld hebben we maar met één actuator te maken. Je controleert of het MQTT bericht data voor een actuator bevat, door de test: if (root.containsKey(<actuatorname>)) {...} . In principe kan een bericht data voor meerdere actuatoren bevatten. Het formaat van de data hangt af van het soort actuator dat je gebruikt.

  if (strcmp(topic, actuatorTopic.c_str())==0) {
    Serial.println("actuator message received");
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.parseObject((char*) payload);
    if (root.success()) {
      if (root.containsKey("led0")) {
        digitalWrite(led0, root["led0"]);
      }
    }
  }

Bemonsteren van sensoren, aansturen van actuatoren

  • pin-definitie van sensoren, timer-definitie
// i/o pin map
const int led0 = 5;
const int button0 = 2;
// analog sensor on A0

long sensor1Timer = 0;
long sensor1Period = 50000; // in millisecs
  • setup van relevante pins (in setup-functie)
void setup() {
  pinMode(led0, OUTPUT);
  pinMode(button0, INPUT);
  analogReference(INTERNAL); //1.1V
  ...
}
  • uitlezen van sensoren (in loop-functie; sommige sensoren gebruiken een timer).
void loop() {
  ...
  if (digitalRead(button0) == HIGH) {
    sensor0Publish();
    delay(200); // limit button repetition rate
  }

  if (millis() >= sensor1Timer) {
    sensor1Publish();
    sensor1Timer = sensor1Timer + sensor1Period;
  }
}
  • aansturen van actuatoren: zie mqttCallback