LoRaWAN IoT-Wetterstation, Erklärung des Programmablaufs


#1

Hi @david,

habe Dein schönes Projekt “LoRaWAN IoT-Wetterstation” nachgebaut und funktioniert prima. Nun habe ich versucht einige Ergänzungen vorzunehmen, ein Display auf dem die einzelnen Messwerte anzuzeigen sowie eine CO2-Messung, die in anderen Programmen sauber funktioniert.
Allerdings habe ich Schwierigkeiten die Anzeigeroutine unterzubringen. Meistens belibt dann das Programm einfach hängen.
Zum Programmverständnis:
die Loop Funktion besteht nur aus einer Anweisung:

Blockquote
os_runloop_once();

Blockquote

was ich übersetzen würde mit: Betriebssystemschleife einmal durchlaufen

Im Setup-Teil gibt es den Aufruf: do_send(&sendjob);
der zu dem eigentlichen Job führt in dem Messwerte gesammelt werden und in der loramessage gebündelt werden.
Wenn ich hier die Anzeigeroutine einfüge ,bleibt das Programm in einer Endlosschleife hängen genauso, wenn ich die Anzeigeroutine ind dem Loop-Teil unterbringe.
Das Ganze scheint mir Eventgesteuert zu sein, aber durch welches Event?

Vielleicht kannst Du mir einige Erklärungen zum Programmablauf geben.

Vielen Dank im Vorraus.


#2

Hi @altprog,

leider kann ich sowas nicht so gut. Ich tagge mal @Felix der hat da mehr Skills :wink:

Freut mich aber mal wieder von dir zu hören :slight_smile:

Liebe Grüße

David


#3

Kannst du mal den Programmcode hier rein posten? Hast du das mit Blockly gemacht oder mit der Arduino IDE? Ich hatte mir es schon mal sowohl in der loop() als auch im do_send() laufen. Eigentlich sollte das kein Problem sein. Allerdings könnte ein delay() in der loop() Probleme bereiten.


#4

Noch eleganter könntest du es lösen indem du einen zweiten Job startest, ähnlich wie der do_send Job.
Dazu musst du oben im Programmcode die entsprechenden Libraries fürs Display einbinden:

#include <SenseBoxMCU.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

und neben dem sendjob einen weiteren Job deklarieren (z.B. displayjob):

static osjob_t sendjob;
static osjob_t displayjob;

Unter dem TX_INTERVAL habe ich ein neues Intervall erstellt, in welchem das Display aktualisiert werden soll:

const unsigned TX_INTERVAL = 60;
const unsigned DISPLAY_INTERVAL = 1; // update display each second

Nach der do_send() Funktion habe ich eine weitere Funktion erstellt, welche den Inhalt des Displays aktualisiert. os_setTimedCallback ruft die Funktion erneut nach DISPLAY_INTERVAL Sekunden auf.
Den counter habe ich ganz am Anfang ebenfalls als Nummer deklariert.

void update_display(osjob_t* t) {
  counter++; // just a simple counter
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.setTextColor(WHITE, BLACK);
  display.print("COUNTER: ");
  display.print(String(counter, DEC));
  display.display();
  os_setTimedCallback(&displayjob, os_getTime() + sec2osticks(DISPLAY_INTERVAL), update_display);
}

Innerhalb der setup Methode starte ich den displayjob nach dem sendjob:

do_send(&sendjob);
update_display(&displayjob);

Somit habe ich die beiden Prozesse quasi parallel laufen.

Einen komplette Demo ist hier zu finden: https://gist.github.com/felixerdy/ceafc6033ae849f4426ba4c1b112d4fa


#5

Hi @Felix,

danke für die beiden Antworten. Hier der Code, den ich benutzt habe, natürlich kommen dort delays vor, was das Ganze etwas erklären würde:

/*   tigerlora sensebox mit Lora Feinstaub und Display
   version 0.3.2 vom 07.09.2020
   mit Schalter für Feinstaubsensor
  senseBox:home - Citizen Sensingplatform
  Version: lorav2.0.0
  Date: 2018-09-11
  Homepage: https://www.sensebox.de https://www.opensensemap.org
  Author: Reedu GmbH & Co. KG
  Note: Sketch for senseBox:home LoRa MCU Edition
  Model: homeV2lora
  Email: support@sensebox.de
  Code is in the public domain.
  https://github.com/sensebox/node-sketch-templater
*/
#include <LoraMessage.h>
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <senseBoxIO.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_HDC1000.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_BME680.h>
#include <Makerblog_TSL45315.h>
#include <VEML6070.h>
#include <SDS011-select-serial.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

// Uncomment the next line to get debugging messages printed on the Serial port
// Do not leave this enabled for long time use
//#define ENABLE_DEBUG

#ifdef ENABLE_DEBUG
#define DEBUG(str) Serial.println(str)
#else
#define DEBUG(str)
#endif

// Schaltermapping
#define sdsSchalter 1   // an pin 1 ist der Schalter für SDS-Sensor angeschlosse
bool status;
bool schalterStatus;
unsigned long int i=0;          //  laufender Zähler
unsigned long int j=0;          //             "
unsigned long t_anzeige=6000;   // 6 Sekunden Anzeigezeit
unsigned long T_display;
float T;  // variable für Temperatur
float H;  //   "    Feuchte
float P;  //   "    Luftdruck
float Lux;//  "     Helligkeit
float UV; //  "     UV
float CO2; //  "    CO2
const unsigned char cmd_get_sensor[] = {0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79 };
unsigned char dataReviced[9];
int temperature_co2;
int CO2PPM;


// Connected sensors
// Temperatur
#define HDC1080_CONNECTED
// rel. Luftfeuchte
#define HDC1080_CONNECTED
// Luftdruck
#define BMP280_CONNECTED
// Beleuchtungsstärke
#define TSL45315_CONNECTED
// UV-Intensität
#define VEML6070_CONNECTED
// PM10
#define SDS011_CONNECTED
// PM2.5
#define SDS011_CONNECTED
// CO2
#define MH-Z16_CONNECTED



// Number of serial port the SDS011 is connected to. Either Serial1 or Serial2
#ifdef SDS011_CONNECTED
#define SDS_UART_PORT (Serial2)
#endif

//Load sensors / instances
#ifdef HDC1080_CONNECTED
  Adafruit_HDC1000 HDC = Adafruit_HDC1000();
  float temperature = 0;
  float humidity = 0;
#endif
#ifdef BMP280_CONNECTED
  Adafruit_BMP280 BMP;
  double pressure;
#endif
#ifdef TSL45315_CONNECTED
  uint32_t lux;
  Makerblog_TSL45315 TSL = Makerblog_TSL45315(TSL45315_TIME_M4);
#endif
#ifdef VEML6070_CONNECTED
  VEML6070 VEML;
  uint16_t uv;
#endif
#ifdef SDS011_CONNECTED
  SDS011 SDS(SDS_UART_PORT);
  float pm10 = 0;
  float pm25 = 0;
#endif
#ifdef SMT50_CONNECTED
  #define SOILTEMPPIN 1
  #define SOILMOISPIN 2
#endif
#ifdef SOUNDLEVELMETER_CONNECTED
  #define SOUNDMETERPIN 3
#endif
#ifdef BME680_CONNECTED
  Adafruit_BME680 BME;
#endif
#ifdef WINDSPEED_CONNECTED
  #define WINDSPEEDPIN 5
#endif



// This EUI must be in little-endian format, so least-significant-byte (lsb)
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes.
static const u1_t PROGMEM DEVEUI[8]= {  };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}

// This EUI must be in little-endian format, so least-significant-byte (lsb)
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]= {   };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}

// This key should be in big endian format (msb) (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = {   };
void os_getDevKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);}

//----------------------- Unterprogramme ----------------------

void zeit() {
  for(i=0;i<=t_anzeige;i++) {
    for(j=0;j<=t_anzeige;j++) {
      
    }
  }
}

void anzeige()  {
  
      // temperatur anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    T_display=int(T);
    display.println((("Temperatur")+String(temperature)));  //Ausgabe auf Display
    display.display();
    delay(300);
    zeit();      


   

        // Feuchte anzeigen
        display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println((("Feuchte:  ")+String(humidity)));  //Ausgabe auf Display
    display.display();
    delay(300);
    zeit();



      //Helligkeit anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println((("Helligk.:  ")+String(lux)));  //Ausgabe auf Display
    display.display();
    delay(300);
    zeit();


      // UV anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println((("UV:       ")+String(uv)));  //Ausgabe auf Display
    display.display();
    delay(300);
    Serial.print("UV: "); Serial.println(UV);
    zeit();


      // CO2 anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    CO2=CO2PPM;
    display.println((("CO2:       ")+String(CO2)));  //Ausgabe auf Display
    display.display();
    delay(300);
    zeit();


 

      // PM10 anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println((("Temperatur:")+String(pm10)));  //Ausgabe auf Display
    display.display();
    delay(100);


      // PM25 anzeigen
      display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println((("Temperatur:")+String(pm25)));  //Ausgabe auf Display
    display.display();
    delay(100);


 } 



static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = PIN_XB1_CS,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = LMIC_UNUSED_PIN,
  .dio = {PIN_XB1_INT, PIN_XB1_INT, LMIC_UNUSED_PIN},
};

void onEvent (ev_t ev) {
  senseBoxIO.statusGreen();
  DEBUG(os_getTime());
  switch(ev) {
    case EV_SCAN_TIMEOUT:
      DEBUG(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      DEBUG(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      DEBUG(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      DEBUG(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      DEBUG(F("EV_JOINING"));
      break;
    case EV_JOINED:
      DEBUG(F("EV_JOINED"));

      // Disable link check validation (automatically enabled
      // during join, but not supported by TTN at this time).
      LMIC_setLinkCheckMode(0);
      break;
    case EV_RFU1:
      DEBUG(F("EV_RFU1"));
      break;
    case EV_JOIN_FAILED:
      DEBUG(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      DEBUG(F("EV_REJOIN_FAILED"));
      break;
    case EV_TXCOMPLETE:
      DEBUG(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      if (LMIC.txrxFlags & TXRX_ACK)
        DEBUG(F("Received ack"));
      if (LMIC.dataLen) {
        DEBUG(F("Received "));
        DEBUG(LMIC.dataLen);
        DEBUG(F(" bytes of payload"));
      }
      // Schedule next transmission
      os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
      break;
    case EV_LOST_TSYNC:
      DEBUG(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      DEBUG(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // data received in ping slot
      DEBUG(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      DEBUG(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      DEBUG(F("EV_LINK_ALIVE"));
      break;
    default:
      DEBUG(F("Unknown event"));
      break;
  }
}

void do_send(osjob_t* j){
  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND) {
    DEBUG(F("OP_TXRXPEND, not sending"));
  } else {
    LoraMessage message;

    //-----Temperature-----//
    //-----Humidity-----//
    #ifdef HDC1080_CONNECTED
      DEBUG(F("Temperature: "));
      temperature = HDC.readTemperature();
      DEBUG(temperature);
      message.addUint16((temperature + 18) * 771);
      delay(2000);

      DEBUG(F("Humidity: "));
      humidity = HDC.readHumidity();
      DEBUG(humidity);
      message.addHumidity(humidity);
      delay(2000);
    #endif

    //-----Pressure-----//
    #ifdef BMP280_CONNECTED
      float altitude;
      pressure = BMP.readPressure()/100;
      altitude = BMP.readAltitude(1013.25); //1013.25 = sea level pressure
      DEBUG(F("Pressure: "));
      DEBUG(pressure);
      message.addUint16((pressure - 300) * 81.9187);
      delay(2000);
    #endif

    //-----Lux-----//
    #ifdef TSL45315_CONNECTED
      DEBUG(F("Illuminance: "));
      lux = TSL.readLux();
      DEBUG(lux);
      message.addUint8(lux % 255);
      message.addUint16(lux / 255);
      delay(2000);
    #endif

    //-----UV intensity-----//
    #ifdef VEML6070_CONNECTED
      DEBUG(F("UV: "));
      uv = VEML.getUV();
      DEBUG(uv);
      message.addUint8(uv % 255);
      message.addUint16(uv / 255);
      delay(2000);
    #endif

    //-----PM-----//
    #ifdef SDS011_CONNECTED
      uint8_t attempt = 0;
      while (attempt < 5) {
        bool error = SDS.read(&pm25, &pm10);
        if (!error) {
          DEBUG(F("PM10: "));
          DEBUG(pm10);
          message.addUint16(pm10 * 10);
          DEBUG(F("PM2.5: "));
          DEBUG(pm25);
          message.addUint16(pm25 * 10);
          break;
        }
        attempt++;
      }
    #endif

    //-----Soil Temperature & Moisture-----//
    #ifdef SMT50_CONNECTED
      float voltage = analogRead(SOILTEMPPIN) * (3.3 / 1024.0);
      float soilTemperature = (voltage - 0.5) * 100;
      message.addUint16((soilTemperature + 18) * 771);
      voltage = analogRead(SOILMOISPIN) * (3.3 / 1024.0);
      float soilMoisture = (voltage * 50) / 3;
      message.addHumidity(soilMoisture);
    #endif

    //-----dB(A) Sound Level-----//
    #ifdef SOUNDLEVELMETER_CONNECTED
      float v = analogRead(SOUNDMETERPIN) * (3.3 / 1024.0);
      float decibel = v * 50;
      message.addUint16(decibel * 10);
    #endif

    //-----BME680-----//
    #ifdef BME680_CONNECTED
      BME.setGasHeater(0, 0);
      if( BME.performReading()) {
        message.addUint16((BME.temperature-1 + 18) * 771);
        message.addHumidity(BME.humidity);
        message.addUint16((BME.pressure/100 - 300) * 81.9187);
      }
      delay(100);
      BME.setGasHeater(320, 150); // 320*C for 150 ms
      if( BME.performReading()) {
        uint16_t gasResistance = BME.gas_resistance / 1000.0;
        message.addUint8(gasResistance % 255);
        message.addUint16(gasResistance / 255);
      }
    #endif

    //-----Wind speed-----//
    #ifdef WINDSPEED_CONNECTED
      float voltageWind = analogRead(WINDSPEEDPIN) * (3.3 / 1024.0);
      float windspeed = 0.0;
      if (voltageWind >= 0.018){
        float poly1 = pow(voltageWind, 3);
        poly1 = 17.0359801998299 * poly1;
        float poly2 = pow(voltageWind, 2);
        poly2 = 47.9908168343362 * poly2;
        float poly3 = 122.899677524413 * voltageWind;
        float poly4 = 0.657504127272728;
        windspeed = poly1 - poly2 + poly3 - poly4;
        windspeed = windspeed * 0.2777777777777778; //conversion in m/s
      }
      message.addUint16(windspeed * 10);
    #endif

    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, message.getBytes(), message.getLength(), 0);
    DEBUG(F("Packet queued"));
  }

  //anzeige();    //Aufruf der Anzeigeroutine
  
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  #ifdef ENABLE_DEBUG
    Serial.begin(9600);
  #endif
  delay(3000);
  schalterStatus=1;     //SDS ein für den  Anfang

  // RFM9X (LoRa-Bee) in XBEE1 Socket
  senseBoxIO.powerXB1(false); // power off to reset RFM9X
  delay(250);
  senseBoxIO.powerXB1(true);  // power on

  // Sensor initialization
  DEBUG(F("Initializing sensors..."));
  #ifdef VEML6070_CONNECTED
    VEML.begin();
    delay(500);
  #endif
  #ifdef HDC1080_CONNECTED
    HDC.begin();
  #endif
  #ifdef BMP280_CONNECTED
    BMP.begin(0x76);
  #endif
  #ifdef TSL45315_CONNECTED
    TSL.begin();
  #endif
  #ifdef SDS011_CONNECTED
    SDS_UART_PORT.begin(9600);
  #endif
  #ifdef BME680_CONNECTED
    BME.begin(0x76);
    BME.setTemperatureOversampling(BME680_OS_8X);
    BME.setHumidityOversampling(BME680_OS_2X);
    BME.setPressureOversampling(BME680_OS_4X);
    BME.setIIRFilterSize(BME680_FILTER_SIZE_3);
  #endif

  DEBUG(F("Sensor initializing done!"));
  DEBUG(F("Starting loop in 3 seconds."));
  
  display.begin(SSD1306_SWITCHCAPVCC,0x3D);
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.clearDisplay();
  delay(1000);
  display.println("   Tiger");
  display.println("   lora");
  display.display();
  delay(3000);
  display.clearDisplay();
  //display.display();

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
}

void loop() {
 /* schalterStatus=digitalRead(sdsSchalter);
 if (status==1) {           //ein?
  if (schalterStatus==0) {     // ausschalten
    SDS.sleep();            // SDS aus
  }
 }else {
                           // nix
 }
 if (status==0) {           //aus?
  if (schalterStatus==1) {    // ein
    SDS.wakeup();
    delay(10000);         // warten 
    
  }
 }
  else {
                            //nix
  }

 DEBUG(F("Schalterstellung: "));
 DEBUG(schalterStatus);
*/
 //anzeige();
  os_runloop_once();
}

Interessant ist Dein Vorschlag einen zweiten Job einzurichten. Das muss ich erstmals studieren, da es ganz neu ist.
Wo kann man mehr über die Parallelprogrammierung mit mehreren Jobs erfahren?

Den Code habe ich von der Einrichtung der senseBox auf der openSensemap in die Arduino IDE übernommen und mit meinen Erweiterungen ergänzt.