Managementul conexiunii WiFi la ESP8266

Circuitul WiFi Espressif ESP8266 a reprezentat o evoluție importantă în universul plăcilor de dezvoltare în general dar și a comunității Arduino. Permițând conectivitate WiFi la un preț derizoriu și ușurință de programare și utilizare în cadrul sistemelor DIY, ESP8266 are o popularitate și o răspândire din ce în ce mai mare echipând plăci de dezvoltare diverse.

Facilitățile oferite de extensia ESP8266 pentru Arduino IDE includ conectarea la Internet prin intermediul unui AP, activarea ca un AP, funcționarea duală client WiFi și AP, utilizarea protocoalelor http și https, funcționarea ca server web sau server DNS ș.a.m.d. Toate aceste facilități oferite de circuitul ESP8266 fac din acesta un instrument extrem de util în cadrul proiectelor proprii. Una dintre problemele care apare totuși la construirea unui dispozitiv bazat pe circuitul ESP8266 este managementul datelor de conectarea la rețea. Dacă se dorește schimbarea datelor de acces la rețeaua WiFi (mutăm dispozitivul în altă locație sau modificăm infrastructura de acces la Internet) dispozitivul trebuie reprogramat. Chiar dacă luăm în calcul programarea OTA tot este necesară utilizarea unui sistem de calcul (PC sau laptop cu Arduino IDE instalat) pentru a modifica datele de acces (SSID și parolă).

Bineînțeles, este posibilă programarea unei interfețe web care să permită modificarea variabilelor corespunzătoare datelor de acces dar acest lucru este destul de consumator de timp și adaptarea codului pentru proiecte diverse este o sarcină repetitivă destul de anevoioasă. Din fericire există o bibliotecă software ce permite automatizarea acestui proces și ușurează foarte mult dezvoltarea noilor proiecte în care avem nevoie de modificarea parametrilor de funcționare fără a reprograma dispozitivul.

Această bibliotecă se numește WiFiManager și permite configurarea, prin intermediul unei interfețe web, a conexiunii WiFi în cazul în care configurarea implicită nu funcționează (interfața web de configurare se activează doar în cazul în care conectarea la rețea nu este posibilă cu datele de autentificare curente). Instalarea bibliotecii se face din Library Manager a mediului Arduino IDE. Testele au fost realizate pe o placă NodeMCU utilizând Arduino IDE 1.8.5 și extensia ESP8266 Community 2.4.1.

2

După instalarea bibliotecii facilitățile de configurare se includ în program inserând în cod secvențele următoare. La începutul programului se include biblioteca WiFiManager și dependințele acesteia:

#include <ESP8266WiFi.h>

#include <DNSServer.h>   

#include <ESP8266WebServer.h> 

#include <WiFiManager.h>

WiFiManager wifiManager;

În cadrul secțiunii setup() se înlocuiește codul obișnuit de conectare cu instrucțiunea:

wifiManager.autoConnect();

Execuția acesteia va conduce la conectarea la rețea dacă procesul de configurare a fost deja parcurs sau la blocarea programului și la pornirea interfeței de configurare web. Aceasta va fi accesibilă prin intermediul unui AP propriu sistemului, AP denumit ESPXXXXXX, la adresa IP 192.168.4.1 .

3

Interfața web permite configurarea și salvarea rețelei în care va funcționa dispozitivul, vizualizarea informațiilor hardware specifice plăcii de dezvoltare și resetarea acesteia.

4

5

Dacă conectivitatea de rețea nu este esențială în funcționarea dispozitivului – dispozitivul trebuie să funcționeze și fără conexiunea de rețea – putem adăuga următoarea instrucțiune pentru a defini un timp în care se așteaptă configurarea după care se trece mai departe la execuția normală a programului.

wifiManager.setConfigPortalTimeout(180);

În cazul în care dorim totuși să reluăm periodic tentativa de conectare la rețea, utilă de exemplu în cazul în care repornirea sistemului coincide cu indisponibilitatea AP-ului – datele de conectare sunt corecte dar în momentul în care se încearcă conectarea nu este disponibilă infrastructura, se poate insera o secvență de instrucțiuni, ca în exemplul de mai jos, în secțiunea loop():

if (WiFi.status() != WL_CONNECTED) wifiManager.autoConnect();

Pagina web de configurare poate fi pornită și în mod voit (la apăsarea unui buton de exemplu) prin intermediul instrucțiunii:

wifiManager.startConfigPortal();

O altă facilitate interesantă oferită de bibliotecă este posibilitatea de a adăuga în cadrul paginii de configurare a unor parametrii proprii ce vor fi salvați în memoria nevolatilă a circuitului ESP8266. Instrucțiunile de mai jos definesc o variabilă server ce permite memorarea adresei de rețea a unui server MQTT. Datele de configurat vor apărea în interfața web alături de configurația de acces în rețeaua WiFi.

WiFiManagerParameter custom_mqtt_server(“server”, “mqtt server”, mqtt_server, 40);

wifiManager.addParameter(&custom_mqtt_server);

mqtt_server = custom_mqtt_server.getValue();

6

Cum putem utiliza prognoza meteo oferită de Weather Underground

Așa cum am arătat și în proiectul „Mini stație meteo”, serviciul Weather Undergroud este un serviciu independent (nu este un serviciu al unei țări sau structuri oficiale) de predicție a vremii. Acest serviciu Internet permite realizarea de prognoze locale pe baza informațiilor provenite de la o stații meteo personale (PWS – Personal Wether Station) sau corelând informațiile de la mai multe stații meteo personale aflate într-o anumită zonă.

Dacă în lecția proiectul „Mini Stație Meteo Weather Underground” am arătat cum să construim o mini stație meteo care să raporteze date către Weather Underground, în lecția de față vom arăta cum putem utiliza datele de prognoză calcultate de Weather Underground. Pentru acest lucru este necesar să solicităm o cheie API (API Key) de conectare de la adresa:

2

Furnizarea prognozei meteo este un serviciu comercial (se plătește) dar pentru dezvoltatori (developers) există un plan tarifar gratuit (Stratus Plan) ce permite efectuarea de 500 de interogări pe zi (maxim 10 interogări pe minut) – limitări ce nu afectează sistemul prezentat în această lecție.

3

Odată obținută cheia de conectare la platforma Weather Underground putem scrie aplicații proprii care să prezinte prognoza meteo fără a fi necesară accesarea site-ului Weather Underground sau utilizarea aplicației mobile Weather Underground. Mai multe informații se pot consulta în pagina de suport a platformei unde sunt prezentate exemple pentru mai multe limbaje de programare. Funcțiile API puse la dispoziție de platforma Weatgher Underground se pot accesa și de pe un sistem embedded simplu. Pentru exemplificarea acestui lucru vom utiliza sistemul descris în proiectul „Ceas IoT cu programare OTA” format dintr-o placă NodeMCU și un ecran LCD grafic 84×48 pixeli:

5

Sistemul va păstra funcționalitatea de ceas și senzor de temperatură IoT dar va afișa și prognoza meteo pentru ziua curentă și ziua următoare.

Vom pleca de la programul scris pentru sistemul Ceas IoT. Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3 având instalată extensia ESP8266 Community 2.3.0. Pentru accesarea funcțiilor API Weather Underground vom utiliza bibliotecile Json Streaming Parser 1.0.5 și ESP8266 Weather Station 1.3.2. Programul Ceas IoT se va completa cu următoarele declarații inițiale (este necesară completarea cheii API – constanta WUNDERGROUND_API_KEY și, dacă este cazul, modificarea localității pentru care se dorește obținerea prognozei meteo – constantele WUNDERGROUND_ZMW_CODE și WUNDERGR_UND_CITY):

#include <JsonListener.h>

#include “WundergroundConditions.h”

#include “WundergroundForecast.h”

const String  WUNDERGRROUND_API_KEY = “…”;

const boolean IS_METRIC = true;

const boolean USE_PM = false;

const String WUNDERGROUND_ZMW_CODE = “00000.26.WLRBS”;

const String  WUNDERGRROUND_LANGUAGE = “EN”;

const String  WUNDERGR_UND_STATE_OR_COUNTRY = “RO”;

const String  WUNDERGR_UND_CITY = “Bucharest”;

WundergroundConditions wunderground(IS_METRIC);

WundergroundForecast wundergroundf(IS_METRIC);

WGConditions conditions;

WGForecast forecasts[3];

La sfârșitul secțiunii setup() se vor adăuga următoarele două instrucțiuni:

wunderground.updateConditions(&conditions, WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGROUND_ZMW_CODE);

wundergroundf.updateForecast(forecasts, 3, WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGR_UND_STATE_OR_COUNTRY, WUNDERGR_UND_CITY);

În cadrul secțiunii loop() se va modifica partea de afișare astfel încât să avem patru stări de afișare (patru ecrane diferite) care se vor succeda la un interval de 15 secunde (fiecare ecran va rămâne afișat timp de 15 secunde): primul ecran va afișa ora și data (la fel ca și în lecția precedentă), al doilea ecran va afișa temperatura interioară și statusul conexiunii IoT (la fel ca și în lecția precedentă), al treilea ecran va afișa prognoza pentru ziua curentă iar al patrulea pentru ziua următoare.

6

7

Din păcate, chiar dacă funcțiile API Weather Underground au implementată traducerea informațiilor în limba română, vom utiliza în program prognoza în limba engleză deoarece setul de caracter a ecranului LCD nu include și diacritice. În capturile anterioare se poate observa că pentru ziua curentă avem cer senin (Clear) și pentru prognoza zilei următoare se preconizează cer noros și câteva picături de ploaie.

if (second()<15) {

       // codul rămâne neschimbat față de proiectul anterior

    }

else if (second()<30) {

      // codul rămâne neschimbat față de proiectul anterior

    }

else if (second()<45) {

      display.drawLine(0,0,83,0,BLACK);

      display.drawLine(0,47,83,47,BLACK);

      display.setTextColor(BLACK);

      display.setTextSize(1);

      display.setCursor(10,2);

      display.print(“Vremea acum”);

      display.drawLine(0,11,83,11,BLACK);

      display.setCursor(30,14);

      display.print(conditions.currentTemp);

      display.print((char)247);

      display.print(“C”);

      display.setCursor(35,22);

      display.print(conditions.humidity);

      display.setCursor(27,30);

      display.print(conditions.pressure);

      display.setCursor((84-(5*conditions.weatherText.length()))/2,38);

      display.print(conditions.weatherText);

      display.display();

    }

    else {

      display.fillScreen(BLACK);

      display.drawLine(0,1,83,1,WHITE);

      display.drawLine(0,46,83,46,WHITE);

      display.setTextColor(WHITE);

      display.setTextSize(1);

      display.setCursor(5,3);

      display.print(“Vremea maine”);

      display.drawLine(0,12,83,12,WHITE);

      display.setCursor(20,14);

      display.print(“Min:”);

      display.print(forecasts[2].forecastLowTemp);

      display.print((char)247);

      display.print(“C”);

      display.setCursor(20,22);

      display.print(“Max:”);

      display.print(forecasts[2].forecastHighTemp);

      display.print((char)247);

      display.print(“C”);

      String temp = forecasts[2].forecastText;

      int prim = temp.indexOf(‘.’);

      display.setCursor((84-(5*prim))/2,30);

      display.print(temp.substring(0,prim));

      display.setCursor(0,38);

     display.print(temp.substring(prim+2, temp.indexOf(‘.’,prim+2)));

      display.display()     }

if (millis() – lastConnectionTime > postingInterval) {

      wunderground.updateConditions(&conditions,

WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGROUND_ZMW_CODE);

      wundergroundf.updateForecast(forecasts, 3, WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGR_UND_STATE_OR_COUNTRY,  WUNDERGR_UND_CITY);

      // codul rămâne neschimbat față de proiectul anterior

    }

Prognoza meteo oferită de Weather Underground are o acoperire de zece zile. În cadrul sistemului prezentat nu am utilizat decât ziua curentă și ziua următoare dar programul poate fi ușor modificat pentru afișarea unei prognoze pe mai multe zile. În plus există mai multe facilități ce nu au fost utilizate în exemplul nostru dar pot constitui dezvoltări ulterioare interesante: avertizări de vreme extremă (a se vedea exemplul WundergroundAlertsDemo din cadrul bibliotecii ESP8266 Weather Station) sau elemente de evoluție astronomică (a se vedea exemplul WundergroundAstronomyDemo).

O altă posibilă direcție de îmbunătățire posibilă a sistemului de predicție meteo este utilizarea simbolurilor grafice oferite de Weather Underground pentru o reprezentare sugestivă a evoluției meteo.

8

Un exemplu de proiect ce utilizează aceste simboluri este „ESP8266 Weather Widget”, proiect ce utilizează un ecran grafic OLED de rezoluție mai mare.

Ceas IoT cu programare OTA

Realizarea unui ceas electronic este un proiect simplu dacă utilizăm o placă de dezvoltare programabilă dar asta nu înseamnă că nu există anumite provocări și în acest caz. Una din provocările majore ale implementării unui ceas electronic este acuratețea menținerii orei (asigurarea orei exacte). Acest lucru se realizează de obicei prin utilizarea unui modul RTC dar în cadrul proiectului de față vom utiliza o placă de dezvoltare NodeMCU care prin conectivitatea WiFi de care dispune ne va permite să realizăm o sincronizare de timp în rețea de tip NTP. Astfel nu vom avea nevoie de nici o componentă suplimentară pentru a asigura ora exactă.

Pentru afișare vom utiliza un ecran LCD grafic cu rezoluția de 84×48 pixeli de tip PCD8544 (LCD de Nokia 5110).

2

Funcționalitatea specială propusă în această lecție este adăugarea sistemului unei funcționalități de tip IoT adică raportarea prin Internet a unui parametru de mediu către o platformă IoT. Sistemul va măsura (și afișa) temperatura și o va trimite pentru înregistrare către platforma Robofun IoT. Astfel sistemul nostru nu va avea simpla funcționalitate de ceas/termometru electronic ci și de senzor IoT. Pentru măsurarea temperaturii vom utiliza un senzor digital brick DS18B20. Acest senzor are o acuratețe mare în măsurarea temperaturii, nu necesită decât 3 fire pentru conectarea la placa de dezvoltare și, cel mai interesant, permite de conectarea (pe aceleași trei fire) a mai multor senzori – sistemul poate fi extins foarte ușor prin adăugarea de noi senzori ce măsoară temperatura în zone diferite (interior / exterior, diverse camere etc.).

3

Ultima funcționalitate interesantă propusă pentru sistemul prezentat în această lecție este posibilitatea de programare la distanță – programare OTA (Over-The-Air). Pentru a reprograma / reîncărca programul nu este nevoie să desfacem carcasa sistemului și să conectăm placa de dezvoltare la calculator ci acest lucru poate fi făcut prin conexiunea WiFi a plăcii de dezvoltare. Singura limitare a acestei metode este că dimensiunea programului nu poate depăși jumătate din memoria program a plăcii de dezvoltare dar nu este cazul sistemului nostru.

4

Interconectarea componentelor (placă de dezvoltare, senzor de temperatură și ecran LCD) este prezentată în diagrama următoare:

5

Senzorul de temperatură va avea pinul D0 conectat la pinul D2 al plăcii de dezvoltare iar pinii 5V și GND la pinii 3.3V și GND ai plăcii de dezvoltare (senzorul funcționează la tensiuni între 3V și 5.5V). Chiar dacă programul prezentat în cele ce urmează este gândit pentru un sistem cu un singur senzor, sistemul acceptă adăugarea pe aceleași 3 fire a mai multor senzori de temperatură pe distanțe de până la 200 de metri.

Ecranul LCD comunică cu placa de dezvoltare prin protocoul SPI și implică următoarele conexiuni:

  • Pinul 1 (RST) al ecranului LCD se conectează la pinul D0 al plăcii de dezvoltare;
  • Pinul 2 (CE) al ecranului la pinul D1 al plăcii de dezvoltare;
  • Pinul 3 (DC) al ecranului la pinul D6 (HMISO) al plăcii de dezvoltare;
  • Pinul 4 (DIN) al ecranului la pinul D7 (HMOSI) al plăcii de dezvoltare;
  • Pinul 5 (CLK) al ecranului la pinul D5 (HSCLK) al plăcii de dezvoltare;
  • Pinul 6 (VCC) al ecranului la un pin de 3.3V al plăcii de dezvoltare;
  • Pinul 7 (LIGHT) al ecranului nu este conectat în schema noastră. Acest pin comandă aprinderea luminii de fundal a ecranului. Se poate conecta la un pin digital liber pentru comandă digitală sau PWM;
  • Pinul 8 (GND) al ecranului la un pin GND al ecranului.

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3 cu extensia ESP8266 Community 2.3.0 instalată. Prima încărcare a programului necesită conectarea plăcii la calculator prin intermediul cablului USB dar ulterior placa va putea fi reprogramată prin OTA (calculatorul de pe care se face programarea trebuie să fie în aceiași rețea ca placa NodeMCU):

6

#include <ESP8266WiFi.h>

#include <ESP8266HTTPClient.h>

#include <ESP8266mDNS.h>

#include <WiFiUdp.h>

#include <ArduinoOTA.h>

Programul utilizează și următoarele biblioteci: Time 1.5.0 – pentru a menține data și ora, sincronizarea NTP se face automat de către bibliotecă la intervale mai mari de timp; OneWire 2.3.3 și DallasTemperature 3.7.6 – pentru comunicația cu senzorul de temperatură; Adafruit GFX 1.2.2 și Adafruit PCD8544 versiune modificată pentru ESP8266 – pentru partea de afișare pe ecranul LCD. În cadrul programului trebuie personalizate datele de conectarea WiFi (variabilele ssid[] și pass[]). Constanta timeZone indică fusul orar (2 sau 3 pentru România, oră de iarnă sau vară).

#include <TimeLib.h>

const int timeZone = 3;

WiFiUDP Udp;

const int NTP_PACKET_SIZE = 48;

byte packetBuffer[NTP_PACKET_SIZE];

unsigned int localPort = 2390;   

char ssid[] = “…”;

char pass[] = “…”;

WiFiClient client;

#define HOSTNAME “ESP8266-OTA-“

#include <DallasTemperature.h>

#include <OneWire.h>

#define ONE_WIRE_BUS D2

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

#include <SPI.h>

#include <Adafruit_GFX.h>

#include <Adafruit_PCD8544.h>

const int8_t RST_PIN = D0;

const int8_t CE_PIN = D1;

const int8_t DC_PIN = D6;

Adafruit_PCD8544 display =

Adafruit_PCD8544(DC_PIN, CE_PIN, RST_PIN);

Secțiunea setup() se va ocupa cu inițializarea conexiunii WiFi (în cazul unei erori pe ecranul LCD va apărea mesajul !WiFi și sistemul va intra într-o buclă de reinițializarea până la conectarea la rețea), inițializarea rețelei OneWire de senzori, inițializarea ecranului și a rutinei de programare OTA. În cazul unei inițializări normale pe ecranul LCD vor apărea informații legate de inițializarea sistemului (denumirea OTA, denumirea rețelei WiFi, adresa IP).

void setup() {

  sensors.begin();

  delay(100);

  display.begin();

  display.setContrast(60);

  display.setTextColor(BLACK);

  display.setCursor(0,0);

  display.setTextSize(1);

  display.clearDisplay();

  display.display();

  String hostname(HOSTNAME);

  hostname += String(ESP.getChipId(), HEX);

  WiFi.hostname(hostname);

  display.println(“Hostname:” + hostname);

  display.display();

  WiFi.begin(ssid, pass);

  delay(5000);

  if (WiFi.status() != WL_CONNECTED) {

    display.clearDisplay();

    display.setCursor(17,16);

    display.setTextSize(2);

    display.print(“!WiFi”);

    display.display();

    delay(10000);

    ESP.restart();

  }

  display.print(“Connected to “);

  display.println(ssid);

  display.print(“IP:”);

  display.println(WiFi.localIP());

  Udp.begin(localPort);

  setSyncProvider(getNtpTime);

  ArduinoOTA.setHostname((const char *)hostname.c_str());

  ArduinoOTA.begin();

  display.display();

  delay(1000);

  display.clearDisplay();

  display.display(); }

Secțiunea loop() se va ocupa cu achiziția parametrului temperatură și cu partea de afișare. Apelarea rutinei de postare IoT se va face la un interval de o oră (constanta postingInterval). Pe ecranul LCD vor apărea alternativ (câte 30 de secunde fiecare) ora/data și temperatura. Ecranul de afișare al temperaturii va conține și mesajul OK/Fail indicând dacă ultima postare IoT s-a efectuat cu succes sau nu (astfel sistemul are și o funcționalitate de supraveghere a bunei funcționări a serviciului IoT).

7

8

unsigned long lastConnectionTime = 0;

const unsigned long postingInterval = 60L * 60L * 1000L;

boolean myiot_ok;

void loop() {

    ArduinoOTA.handle();

    sensors.requestTemperatures();

    delay(100);

    float temperature = sensors.getTempCByIndex(0);

    display.clearDisplay();

    if (second()<30) {

      display.drawRoundRect(2,2,80,44,3,BLACK);

      display.setTextColor(BLACK);

      display.setCursor(13,10);

      display.setTextSize(2);

      printDigits(hour(),false);

      printDigits(minute(),true);

      display.println();

      display.setTextSize(1);

      display.setCursor(13,26);

      display.print(day());

      display.print(“/”);

      display.print(month());

      display.print(“/”);

      display.print(year());

      display.println();

      display.display(); 

    }

    else {

      display.fillScreen(BLACK);

      display.drawRoundRect(2,2,80,44,3,WHITE);

      display.setTextColor(WHITE);

      display.setTextSize(2);

      display.setCursor(22,10);

      display.print((int)temperature);

      display.print((char)247);

      display.println(“C”);

      display.setTextSize(2);

      if (myiot_ora!=-1) {

        if (myiot_ok) { 

          display.setCursor(30,26);

          display.println(“OK”);    }

        else {

          display.setCursor(20,26);

          display.println(“FAIL”); }

      }

      display.display();

    }

    if (millis() – lastConnectionTime > postingInterval) {

      if (IoTpublish(temperature)>0)

        { myiot_ok=true; }

      else

        { myiot_ok=false; }

    }

}

Funcția IoTpublish este responsabilă cu postarea IoT și returnează codul returnat de operația HTTP GET efectuată. În cadrul acesteia trebuie completată cheia de autentificare oferită de serviciul Robofun IoT (variabila SENSOR_TOKEN). Procedura printDigits este folosită pentru afișarea mai simplă a orei.

int IoTpublish(float temperature) {

  String SENSOR_TOKEN = “…”;

  HTTPClient http;

  String data = String(“http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN + “/input?value=” + String(temperature, DEC);

  http.begin(data);

  int httpCode = http.GET();

  delay(100);

  if(httpCode > 0) {

      if(httpCode == HTTP_CODE_OK) {

        String payload = http.getString();

      }

   }

  http.end();

  lastConnectionTime = millis();

  return httpCode;

}

void printDigits(int digits, boolean dots){

   if (dots) display.print(“:”);

  if(digits < 10)

    display.print(‘0’);

  display.print(digits);

}

Funcțiile getNtpTime și sendNTPpacket sunt apelate automat de către biblioteca Time și asigură partea de comunicație NTP. Constanta ntpServerName indică serverul NTP care este utilizat pentru sincronizarea de timp.

IPAddress timeServerIP;

const char* ntpServerName = “time.nist.gov”;

time_t lastsyncr;

time_t getNtpTime() {

  WiFi.hostByName(ntpServerName, timeServerIP);

  while (Udp.parsePacket() > 0) ;

  sendNTPpacket(timeServerIP);

  uint32_t beginWait = millis();

  while (millis() – beginWait < 2500) {

    int size = Udp.parsePacket();

    if (size >= NTP_PACKET_SIZE) {

      Udp.read(packetBuffer, NTP_PACKET_SIZE);

      unsigned long secsSince1900;

      secsSince1900 =  (unsigned long)packetBuffer[40]

<< 24;

      secsSince1900 |= (unsigned long)packetBuffer[41]

<< 16;

      secsSince1900 |= (unsigned long)packetBuffer[42]

<< 8;

      secsSince1900 |= (unsigned long)packetBuffer[43];

      lastsyncr = (time_t) (secsSince1900 – 2208988800UL +

timeZone * SECS_PER_HOUR);

      return lastsyncr;

    }

  }

  return 0;

}

void sendNTPpacket(IPAddress &address) {

  memset(packetBuffer, 0, NTP_PACKET_SIZE);

  packetBuffer[0] = 0b11100011;  

  packetBuffer[1] = 0; 

  packetBuffer[2] = 6; 

  packetBuffer[3] = 0xEC; 

  packetBuffer[12]  = 49;

  packetBuffer[13]  = 0x4E;

  packetBuffer[14]  = 49;

  packetBuffer[15]  = 52;

  Udp.beginPacket(address, 123);

  Udp.write(packetBuffer, NTP_PACKET_SIZE);

  Udp.endPacket(); }

După o oră după punerea în funcțiune a sistemului se pot consulta datele înregistrate în cadrul platformei Robofun IoT. Mai jos este o captură de ecran cu datele înregistrate de către sistemul de test în decursul unei zile.

9

LoRa meets Robofun IoT

Modulele radio LoRa oferă posibilitatea de a transmite date la distanță mare (sute de metri sau chiar kilometri) utilizând module electronice de cost redus și cu un consum de energie scăzut. Acest lucru constituie o metodă eficientă pentru a extinde aria de acoperire pentru rețelele IoT fără fir. Chiar dacă semnalul WiFi are o acoperire limitată fiind influențat de puterea dispozitivelor de tip AP și de mediul în care operează (câmp deschis, locuințe sau birouri) există posibilitatea să extindem aria de acoperire radio a unei rețele IoT prin intermediul comunicațiilor ISM iar soluțiile LoRa oferă o variantă foarte bună cost / arie de acoperire.

Pentru a implementa o soluție LoRa în vederea extinderii ariei de acoperire IoT vom implementa un sistem gateway ce va realiza transferul datelor provenite de la modulele IoT către un sistem specific IoT și anume Robofun IoT. Modulul gateway propus se bazează pe placa de dezvoltare NodeMCU ce oferă conectivitate WiFi și un modul LoRa RFM96W în bandă de 433MHz. Conexiuniile între placa de dezvoltare și modulul radio sunt prezentate în diagrama următoare:

2.png

Modulul radio RFM96W se interconectează cu placa de dezvoltare prin intermediul magistralei SPI:

  • Pinul SCK al modulului se conectează la pinul D5 (GPIO14 – HSCLK);
  • Pinul MISO se conectează la pinul D6 (GPIO12 – HMISO);
  • Pinul MOSI se conectează la pinul D7 (GPIO13 – HMOSI);
  • Pinul CS se conectează la pinul D2;
  • Pinul RST se conectează la pinul D3;
  • Pinul G0 (INT) se conectează la pinul D1;
  • Vin și GND la pinii 3.3V și GND ai plăcii de dezvoltare.

Pentru ca placa să poată transmite prin Internet datele către serviciul Robofun IoT este necesară înregistrarea gratuită pe platformă:

3

După înregistrare și conectare este necesară definirea (Adauga senzor) a trei senzori: Temperatura, Umiditate și Nivel baterie, pentru a putea transmite datele primite de la sistemul IoT către platforma online. După definirea fiecărui senzor este necesar să copiem cheia de autentificare (Token) pentru a fi utilizată în program.

4

5

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3 (pentru instalarea plăcii NodeMCU sub Arduino IDE se poate vedea materialul „Quick Start to Nodemcu (ESP8266) on Arduino IDE”), extensia esp8266 versiunea 2.3.0 și biblioteca RadioHead 1.7.9. În cadrul programului trebuie personalizate datele de conectare la rețeaua WiFi (ssid și password) precum și cheile de autentificare pentru cele trei canale IoT (SENSOR_TOKEN1, SENSOR_TOKEN2 și SENSOR_TOKEN3).

#include <SPI.h>

#include <RH_RF95.h>

#include <ESP8266WiFi.h>

#include <ESP8266HTTPClient.h>

const char* ssid = “…”;

const char* password = “…”;

#define RFM95_CS D2

#define RFM95_RST D3

#define RFM95_INT D1

#define RF95_FREQ 434.0

RH_RF95 rf95(RFM95_CS, RFM95_INT);

#define LED D0

void setup() {

  pinMode(LED, OUTPUT);

  digitalWrite(LED,HIGH);    

  pinMode(RFM95_RST, OUTPUT);

  digitalWrite(RFM95_RST, HIGH);

  Serial.begin(9600);

  delay(100);

  Serial.println();

  Serial.println();

  Serial.println(“Gateway Module starting…”);

  Serial.print(“Connecting to “);

  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

      delay(500);

      Serial.print(“.”);

    }

    Serial.println(“”);

    Serial.println(“WiFi connected”);

  digitalWrite(RFM95_RST, LOW);

  delay(10);

  digitalWrite(RFM95_RST, HIGH);

  delay(10);

  while (!rf95.init()) {

    Serial.println(“LoRa radio init failed”);

    while (1);

  }

  Serial.println(“LoRa radio init OK!”);

  if (!rf95.setFrequency(RF95_FREQ)) {

    Serial.println(“setFrequency failed”);

    while (1);

  }

  Serial.print(“Set Freq to: “);

  Serial.println(RF95_FREQ);

}

typedef struct {float temperature; float humidity;

float bat_voltage;} ParametriiRX;

ParametriiRX parametrii;

Ledul de pe placa de dezvoltare (conectat pe pinul D0) se va aprinde pe perioada recepției de mesaje LoRa. Toate datele primite prin intermediul LoRa vor fi postate pe platforma Robofun IoT. Sistemul va raporta și în consola serială toate mesajele recepționate.

6

void loop() {

  if (rf95.available())  {

    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];

    uint8_t len = sizeof(buf);

    if (rf95.recv(buf, &len)){

      digitalWrite(LED, LOW);

      RH_RF95::printBuffer(“Received: “, buf, len);

      parametrii = *(ParametriiRX*)buf;

      Serial.print(“Temperature: “);

      Serial.println(parametrii.temperature);

      Serial.print(“Humidity: “);

      Serial.println(parametrii.humidity);

      Serial.print(“Battery Voltage: “);

      Serial.println(parametrii.bat_voltage);

      Serial.print(“RSSI: “);

      Serial.println(rf95.lastRssi(), DEC);

      digitalWrite(LED, HIGH);

      String SENSOR_TOKEN1=”…”;

      String SENSOR_TOKEN2=”…”;

      String SENSOR_TOKEN3=”…”;

      HTTPClient http;

      String data = String(“http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN1 + “/input?value=” + String(parametrii.temperature, DEC);

      http.begin(data);

      int httpCode = http.GET();

      http.end();

      data = String(“http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN2 + “/input?value=” + String(parametrii.humidity, DEC);

      http.begin(data);

      httpCode = http.GET();

      http.end();

      data = String(“http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN3 +  “/input?value=” + String(parametrii.bat_voltage, DEC);

      http.begin(data);

      httpCode = http.GET();

      http.end();    }

    else    {

      Serial.println(“Receive failed”);    }

  }

}

Modulul LoRa ce va beneficia de extinderea ariei de comunicație (se poate afla la sute de metri, sau chiar kilometri în spațiu deschis, de aria de acoperire WiFi) se bazează pe placa de dezvoltare Feather M0 RFM95 433MHz LoRa – placă ce combină puterea unui microcontroler ARM Cortex-M0+ ATSAMD21 (la fel ca și plăcile Arduino M0/Zero) cu conectivitatea unui modul radio LoRa. Placa de dezvoltare va transmite către modulul gateway date preluate de la un senzor digital I2C de temperatură și umiditate Si7021 precum și nivelul bateriei proprii (unul dintre avantajele majore ale plăcilor Feather este posibilitatea de alimentare mobilă de la un acumulator LiPo de 3.7V).

Pentru mai multe informații legate de utilizarea plăcii Feather M0 RFM95 puteți consulta și materialul: „Adafruit Feather M0 Radio with LoRa Radio Module”.

Schema de interconectare între senzor și placa de dezvoltare este specifică unei magistrale I2C (pinii SDA și SCL conectați între senzor și placa de dezvoltare, alimentarea senzorului se va face la 3.3V):

7

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3, extensia Adafruit SAMD Boards 1.0.19 și bibliotecile RadioHead 1.7.9, Adafruit Si7021.

Decomentarea directivei debug va permite observarea funcționării modulului în consola serială.

//#define debug

7.5

#include <SPI.h>

#include <RH_RF95.h>

#define VBATPIN A7

#include <Adafruit_Si7021.h>

Adafruit_Si7021 sensor = Adafruit_Si7021();

#define RFM95_CS 8

#define RFM95_RST 4

#define RFM95_INT 3

#define RF95_FREQ 434.0

RH_RF95 rf95(RFM95_CS, RFM95_INT);

void setup() {

  pinMode(RFM95_RST, OUTPUT);

  digitalWrite(RFM95_RST, HIGH);

  sensor.begin();

  #ifdef debug

    while (!Serial);

    Serial.begin(9600);

    delay(100);

    Serial.println(“LoRa Sensor Module starting…”);

  #endif

  digitalWrite(RFM95_RST, LOW);

  delay(10);

  digitalWrite(RFM95_RST, HIGH);

  delay(10);

  while (!rf95.init()) {

    #ifdef debug

      Serial.println(“LoRa radio init failed”);

    #endif

    while (1);

  }

  #ifdef debug

    Serial.println(“LoRa radio init OK!”);

  #endif

  if (!rf95.setFrequency(RF95_FREQ)) {

    #ifdef debug

      Serial.println(“setFrequency failed”);

    #endif

    while (1);

  }

  #ifdef debug

    Serial.print(“Set Freq to: “);

    Serial.println(RF95_FREQ);

  #endif

  rf95.setTxPower(23, false);

}

typedef struct {float temperature; float humidity;

float bat_voltage;} ParametriiTX;

ParametriiTX parametrii;

void loop() {

  float voltage = analogRead(VBATPIN);

  voltage *= 2;   

  voltage *= 3.3;

  voltage /= 1024;

  parametrii.bat_voltage = voltage;

  parametrii.temperature = sensor.readTemperature();

  parametrii.humidity = sensor.readHumidity();

  #ifdef debug

    Serial.print(“Sample OK: “);

    Serial.print(parametrii.temperature);

   Serial.print(” *C, “);

    Serial.print(parametrii.humidity);

    Serial.println(” %”);

    Serial.print(“VBat: ” ); Serial.println(voltage);

    Serial.println(“Sending to Gateway Module”);

    Serial.println(“Sending…”); delay(10);

  #endif

  RH_RF95::printBuffer(“Sending: “, (uint8_t*)&parametrii, sizeof parametrii);

  #ifdef debug

    Serial.println(“Sending…”); delay(10);

  #endif

  rf95.send((uint8_t *)&parametrii, sizeof parametrii);

  #ifdef debug

    Serial.print(“Waiting for packet to complete…”);

delay(10);

  #endif

  rf95.waitPacketSent();

  #ifdef debug

    Serial.println(“done.”); delay(10);

  #endif

  delay(100);

  rf95.sleep();

  delay(60000);

}

După programarea și punerea în funcțiune a celor două sisteme se pot observa datele înregistrate în cadrul platformei Robofun IoT (capturile de ecran de mai jos reprezintă date înregistrate de sistemul de test pentru temperatură și umiditate).

8

9

Bineînțeles, sistemul LoRa poate deservi atât sisteme de achiziție (temperatură, umiditate, presiunea în diverse conducte, consumul de energie electrică, nivelul radiației solare etc.) dar și sisteme de acționare (chiar dacă nu a fost exemplificată această parte este posibil să comandăm de la distanță diverse mecanisme de închidere / deschidere, motoare etc).