LoRa P2P, peer-to-peer, DIY weather station

The weather station consists of two parts, a sender and a receiver. The sender is installed outdoors. It sends every half an hour the data via the LoRa p2p, peer-to-peer, protocol to the receiver, where it is displayed on the i-ink display.

Previously I had a weather station, which was sending the data via WiFi. The range of WiFi is quite limited, besides it becomes even shorter when the window is closed. With this LoRa weather station I do not have any issues with the range or with the closed windows. The signal reaches any place of my apartment.

The current of the sender is less than 1 mAh. It is possible to reduce it by switching off the OLED display on the WiFi LoRa 32 (V2) Heltec micro-controller. Also it would be enough to send weather data not every 30 minutes, but every two hours.

For now I leave it 30 minutes, because I plan to improve the code further. So I will need to do some more testing.

The receiver works either on the Li-Ion accumulator or via the cable connected to a usual 5 volts USB outlet. If the cable is connected to the USB, the accumulator inside is charging.

The BME680 sensor is connected to the following pins SCL to SCL, pin 22, SDA to SDA, pin 21. The e-paper display is connect to: blue wire, DIN, to pin 27; yellow, CLK to pin 5; orange, CS, to pin 18; green, DC, to pin 22, white, RST is not connected to anything; violet, BUSY, to pin 23. Both BME680 sensor and the e-ink display get the power from the 3.3 V pins of the board.

The antenna is installed on the box with the hot glue, in order to prevent water getting inside. I placed a small packet with silica gel inside the sender box to prevent humidity accumulating inside. The BME680 sensor is installed inside a small plastic bottle what will protect it from the precipitation. I include some photos, - click on the photo to open the HD image in the new tab of the browser. The complete programming code of the sender and the receiver is also included below on this page.

Here is the list of the components used:

Receiver with the e-ink display.
Receiver with the opened lid.
Sender installed outdoors.
Sender part of the weather station with the open lid
Sender.
Sender with the lid open.
BME-680 sensor.
Lora Wether station sender code
    
      #include "heltec.h"
      #include <Wire.h>
      #include <SPI.h>
      #include <Adafruit_Sensor.h>
      #include "Adafruit_BME680.h"

      #define SEALEVELPRESSURE_HPA (1013.25)

      Adafruit_BME680 bme; // I2C


      #define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
      #define TIME_TO_SLEEP  1800        /* Time ESP32 will go to sleep (in seconds) */

      #define BAND    868E6  //you can set band here directly,e.g. 868E6,915E6

      unsigned int counter = 0;
      String rssi = "RSSI --";
      String packSize = "--";
      String packet ;

      RTC_DATA_ATTR int bootCount = 0;

      void logo()
      {
        Heltec.display->clear();

      }

      /*
        Method to print the reason by which ESP32
        has been awaken from sleep
      */
      void print_wakeup_reason() {
        esp_sleep_wakeup_cause_t wakeup_reason;

        wakeup_reason = esp_sleep_get_wakeup_cause();

        switch (wakeup_reason)
        {
          case 1  :
            {
              Serial.println("Wakeup caused by external signal using RTC_IO");
              delay(2);
            } break;
          case 2  :
            {
              Serial.println("Wakeup caused by external signal using RTC_CNTL");
              delay(2);
            } break;
          case 3  :
            {
              Serial.println("Wakeup caused by timer");
              delay(2);
            } break;
          case 4  :
            {
              Serial.println("Wakeup caused by touchpad");
              delay(2);
            } break;
          case 5  :
            {
              Serial.println("Wakeup caused by ULP program");
              delay(2);
            } break;
          default :
            {
              Serial.println("Wakeup was not caused by deep sleep");
              delay(2);
            } break;
        }
      }

      void setup() {

        Serial.begin(9600);
        while (!Serial);
        Serial.println(F("BME680 test"));


        if (!bme.begin()) {
          Serial.println("Could not find a valid BME680 sensor, check wiring!");
          while (1);
        }

        // Set up oversampling and filter initialization
        bme.setTemperatureOversampling(BME680_OS_8X);
        bme.setHumidityOversampling(BME680_OS_2X);
        bme.setPressureOversampling(BME680_OS_4X);
        bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
        bme.setGasHeater(320, 150); // 320*C for 150 ms

        if (! bme.performReading()) {
          Serial.println("Failed to perform reading :(");
          return;
        }
        Serial.print("Temperature = ");
        Serial.print(bme.temperature);
        Serial.println(" *C");

        Serial.print("Pressure = ");
        Serial.print(bme.pressure / 100.0);
        Serial.println(" hPa");

        Serial.print("Humidity = ");
        Serial.print(bme.humidity);
        Serial.println(" %");

        Serial.print("Gas = ");
        Serial.print(bme.gas_resistance / 1000.0);
        Serial.println(" KOhms");

        Serial.print("Approx. Altitude = ");
        Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
        Serial.println(" m");

        Serial.println();
        delay(300);

        String msg = "";

        msg = String(bootCount) + "#" + String(bme.temperature) + "#" + String(bme.pressure / 100.0) + "#" + String(bme.humidity) + "#" + String(bme.gas_resistance / 1000.0) + "#" + String(bme.readAltitude(SEALEVELPRESSURE_HPA));



        //WIFI Kit series V1 not support Vext control
        Heltec.begin(true /*DisplayEnable Enable*/, true /*LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);

        Heltec.display->init();
        Heltec.display->flipScreenVertically();
        Heltec.display->setFont(ArialMT_Plain_10);
        delay(1500);
        Heltec.display->clear();

        Heltec.display->drawString(0, 0, "Heltec.LoRa Initial success!");
        Heltec.display->display();
        delay(1000);

        //Increment boot number and print it every reboot
        ++bootCount;
        Serial.println("Boot number: " + String(bootCount));
        delay(2);

        Heltec.display->clear();
        Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
        Heltec.display->setFont(ArialMT_Plain_10);

        Heltec.display->drawString(0, 0, "Sending packet: ");
        Heltec.display->drawStringMaxWidth(0 , 11 , 128, msg);

        Heltec.display->display();

        // send packet
        LoRa.beginPacket();

        /*
           LoRa.setTxPower(txPower,RFOUT_pin);
           txPower -- 0 ~ 20
           RFOUT_pin could be RF_PACONFIG_PASELECT_PABOOST or RF_PACONFIG_PASELECT_RFO
             - RF_PACONFIG_PASELECT_PABOOST -- LoRa single output via PABOOST, maximum output 20dBm
             - RF_PACONFIG_PASELECT_RFO     -- LoRa single output via RFO_HF / RFO_LF, maximum output 14dBm
        */
        LoRa.setTxPower(14, RF_PACONFIG_PASELECT_PABOOST);

        LoRa.print(msg);
        LoRa.endPacket();

        counter++;
        digitalWrite(LED, HIGH);   // turn the LED on (HIGH is the voltage level)
        delay(1000);                       // wait for a second
        digitalWrite(LED, LOW);    // turn the LED off by making the voltage LOW
        delay(1000);

        //Print the wakeup reason for ESP32
        print_wakeup_reason();




        /*
          First we configure the wake up source
          We set our ESP32 to wake up every 5 seconds
        */
        esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
        Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
                       " Seconds");
        delay(10);

        /*
          Next we decide what all peripherals to shut down/keep on
          By default, ESP32 will automatically power down the peripherals
          not needed by the wakeup source, but if you want to be a poweruser
          this is for you. Read in detail at the API docs
          http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html
          Left the line commented as an example of how to configure peripherals.
          The line below turns off all RTC peripherals in deep sleep.
        */
        //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
        //Serial.println("Configured all RTC Peripherals to be powered down in sleep");

        /*
          Now that we have setup a wake cause and if needed setup the
          peripherals state in deep sleep, we can now start going to
          deep sleep.
          In the case that no wake up sources were provided but deep
          sleep was started, it will sleep forever unless hardware
          reset occurs.
        */
        LoRa.end();
        LoRa.sleep();
        delay(100);


        pinMode(5, INPUT);

        pinMode(14, INPUT);
        pinMode(15, INPUT);
        pinMode(16, INPUT);
        pinMode(17, INPUT);
        pinMode(18, INPUT);
        pinMode(19, INPUT);

        pinMode(26, INPUT);
        pinMode(27, INPUT);


        delay(100);
        Serial.println("Going to sleep now");
        delay(2);
        esp_deep_sleep_start();
        Serial.println("This will never be printed");
      }

      void loop() {
        //This is not going to be called
      }
    
  
Lora Wether station receiver code
    
        #include "heltec.h"
        #include "images.h"

        // base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
        // enable or disable GxEPD2_GFX base class
        #define ENABLE_GxEPD2_GFX 0

        #include <GxEPD2_BW.h>
        #include <GxEPD2_3C.h>
        #include <Fonts/FreeMonoBold9pt7b.h>
        #include <U8g2_for_Adafruit_GFX.h>

        GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(/*CS=5*/ 18, /*DC=*/ 22, /*RST=*/ 16, /*BUSY=*/ 23));
        // 2.9 inches display
        //GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(/*CS=5*/ 18, /*DC=*/ 22, /*RST=*/ 16, /*BUSY=*/ 23));

        U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;

        #define BAND    868E6  //you can set band here directly,e.g. 868E6,915E6
        String rssi = "RSSI --";
        String packSize = "--";
        String packet;

        // for first split of the whole message
        #define MAX_PARTS_COUNT 6
        #define MIN_PARTS_COUNT 2
        char *msgParts[MAX_PARTS_COUNT];
        char *StringToParse;
        byte parts_count;

        void logo() {
          Heltec.display->clear();
          Heltec.display->drawXbm(0, 5, logo_width, logo_height, logo_bits);
          Heltec.display->display();
        }

        void LoRaData() {
          Heltec.display->clear();
          Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
          Heltec.display->setFont(ArialMT_Plain_10);


          Heltec.display->drawStringMaxWidth(0 , 26 , 128, packet);

          Heltec.display->drawString(0, 0, rssi);
          Heltec.display->display();

          // converting strig to a char?
          StringToParse =  (char*) packet.c_str();

          parts_count = split_message(StringToParse, "#");


          if (parts_count > 0) {

            //Serial.println("myReading");
            uint16_t bg = GxEPD_WHITE;
            uint16_t fg = GxEPD_BLACK;
            u8g2Fonts.setFontMode(1);                 // use u8g2 transparent mode (this is default)
            u8g2Fonts.setFontDirection(0);            // left to right (this is default)
            u8g2Fonts.setForegroundColor(fg);         // apply Adafruit GFX color
            u8g2Fonts.setBackgroundColor(bg);


            display.firstPage();
            do
            {

              display.fillScreen(bg);

              parts_count = split_message(StringToParse, "#");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font; select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
              u8g2Fonts.setCursor(0, 25); // start writing at this position
              u8g2Fonts.print("Temperature: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[1]);
              u8g2Fonts.print(" ÂșC");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font
              u8g2Fonts.setCursor(0, 55); // start writing at this position
              u8g2Fonts.print("Pressure: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[2]);
              u8g2Fonts.print(" hPa");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font
              u8g2Fonts.setCursor(0, 85); // start writing at this position
              u8g2Fonts.print("Humidity: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[3]);
              u8g2Fonts.print(" %");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font
              u8g2Fonts.setCursor(0, 115); // start writing at this position
              u8g2Fonts.print("Gas res.: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[4]);
              u8g2Fonts.print(" KOhms");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font;
              u8g2Fonts.setCursor(0, 145); // start writing at this position
              u8g2Fonts.print("Altitude: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[5]);
              u8g2Fonts.print(" m");

              u8g2Fonts.setFont(u8g2_font_helvR10_tf);  // normal font
              u8g2Fonts.setCursor(0, 175); // start writing at this position
              u8g2Fonts.print("LoRa message #: ");

              u8g2Fonts.setFont(u8g2_font_helvB14_tf); // bold font
              u8g2Fonts.print(msgParts[0]);

            }
            while (display.nextPage());

          }

        }

        void cbk(int packetSize) {
          packet = "";
          packSize = String(packetSize, DEC);
          for (int i = 0; i < packetSize; i++) {
            packet += (char) LoRa.read();
          }
          rssi = "RSSI " + String(LoRa.packetRssi(), DEC) ;
          LoRaData();
        }

        void setup() {

          Serial.begin(9600);

          display.init();
          u8g2Fonts.begin(display); // connect u8g2 procedures to Adafruit GFX
          display.setRotation(1);

          //WIFI Kit series V1 not support Vext control
          Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.Heltec.Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);

          Heltec.display->init();
          Heltec.display->flipScreenVertically();
          Heltec.display->setFont(ArialMT_Plain_10);
          logo();
          delay(1500);
          Heltec.display->clear();

          Heltec.display->drawString(0, 0, "Heltec.LoRa Initial success!");
          Heltec.display->drawString(0, 10, "Wait for incoming data...");
          Heltec.display->display();
          delay(1000);

          //LoRa.onReceive(cbk);
          LoRa.receive();
        }

        void loop() {
          int packetSize = LoRa.parsePacket();
          if (packetSize) {
            cbk(packetSize);
          }
          delay(10);
        }


        byte split_message(char* str, char * dlm) {

          byte parts_count = 0;

          char * item = strtok (str, dlm); //getting first word (uses space & comma as delimeter)

          while (item != NULL) {

            if (parts_count >= MAX_PARTS_COUNT) {
              break;
            }

            msgParts[parts_count] = item;

            item = strtok (NULL, dlm); //getting subsequence word

            parts_count++;

          }
          return  parts_count;
        }
    
  

January 4, 2020. Contact me at: webmaster[[at]]ausleuchtung.ch

Add Comment

* Required information
1000
Enter the word shark backwards.
Captcha Image
Powered by Commentics

Comments (1)

Gravatar
New
Felix(Ireland)says...

Thank you! I ordered the same components. I would like to ask how did you fix the battery holder inside?

Gravatar
New
Alexis(Switzerland)says...

I used the double sided tape. It holds the accumulator good enough.