Shop OBEX P1 Docs P2 Docs Learn Events
Environmental Sensors using LoraWan — Parallax Forums

Environmental Sensors using LoraWan

First off I purchased two of these units:

Wio-E5 units are very low power with a range of several miles. In fact they say it can reach 500 miles given the right environment say like the Spy Ballon for example.

Just like my previous cellular project it uses serial at 9600 to communicate with the unit. The Grove connector it came with is not very nice in that it's big and just a ribbon cable. Not very flexible.

I ended up cutting one end off and putting a 4 pin connector in its place. Plugs in very nicely.

The unit uses the AT command set to configure it and I was hoping to just do a Point to Point Lora network but that turned out to be more complicated than I thought. The units want to setup a Wan network and so you need a Gateway for it to relay the messages to the cloud. Well, that's not that much different than my Cellular project and the cost of Gateway is from $90 to $1000.

The cloud network that I can use is called The Things Network.

It's free to sign up just like the Blues network and they will collect your data for up to 24hours.

The data for Lora is in hex and the data must be less than 51 bytes in length. So no JSON data should be passed between the device and the network. The examples I saw just encode the hex values of the data and have it in the correct order so it can be decoded once it is received.

This turned out to be ok. I have Temperature, Humidity, Pressure, Voltage, and Current.

Float values are stored as 4 bytes just like integer values so I just converted those binary values to hex and sent them which takes only 20 bytes.

Now I have everything ready except for a Gateway. Most of the Gateways are out of stock or expensive.
I will have to wait to get one since there are no local gateways running near my location that I could hop onto. The LoraWan network is more of European thing at this point.

I was able to dummy up the two devices to do Point-to-Point. This at least allowed me to test sending and receiving the data. With LoraWan the data is encrypted from end to end and the Gateway just receive the packet of data and forewords it on to the cloud where it is decrypted using 128 bit AES. When you register the device it gives you a key that is entered into the device. The data is encrypted/decrypted with this key.

So just like the Blues network the data is encrypted but not guaranteed to make it.

Mike

Comments

  • Her is my setup for the Environment sensor which I just replaced the Blues Notecard with the Wio-E5 Lora board.

    The code is pretty much the same I made the driver which is also mostly a full duplex serial driver

    #include <stdio.h>
    #include <propeller.h>
    #include "i2c.h"
    #include "ina260.h"
    #include "bme68x.h"
    #include "blueio.h"
    #include "json.h"
    #include "wioe5.h"
    
    
    #define INACLK 8
    #define INADTA 9
    #define BLURX 20
    #define BLUTX 21
    #define BLUAT 22
    #define LRTX 20
    #define LRRX 21
    #define BMECLK 36
    #define BMEDTA 37
    #define PWRPN 15
    
    
    void StartMonitor(void);
    void StartBlue(void);
    void StartLora(void);
    void SendLora(char*);
    void StartBME(void);
    int GetEnvironment(float *, float *, float *);
    void Sleep(int);
    void Hex(void *);
    
    
    char Buffer[1024];
    char Convert[64];
    char fmt[] = "%4.2f";
    i2c_t *Bme;
    char dev_addr;
    struct bme68x_dev gas_sensor;
    struct bme68x_conf conf;
    struct bme68x_heatr_conf heatr_conf;
    struct bme68x_data data[3];
    
    
    int main(int argc, char** argv)
    {
        float t, h, p;
        int v, c;
    
        _pinl(56);
        _pinl(57);
    
        /* Enable Power Switch */
        _pinh(PWRPN);
    
        /* 20Mhz for lower power use */
        _clkset(0x010000fb, 20000000);
    
        StartMonitor();
        StartBME();
        //StartBlue();
        StartLora();
    
        _pinl(56);
        _pinl(57);
    
        while (1)
        {
            GetEnvironment(&t, &h, &p);
            v = INA260_getVoltage();
            c = INA260_getCurrent();
            Buffer[0] = 0;
            Hex(&t);
            Hex(&h);
            Hex(&p);
            Hex(&v);
            Hex(&c);
            WIOE5_SendLora(Buffer);
            json_init(Buffer);
            sprintf(Convert, fmt, t);
            json_putDec("temperature", Convert);
            sprintf(Convert, fmt, h);
            json_putDec("humidity", Convert);
            sprintf(Convert, fmt, p);
            json_putDec("pressure", Convert);
            sprintf(Convert, "%d", v);
            json_putDec("voltage", Convert);
            sprintf(Convert, "%d", c);
            json_putDec("current", Convert);
            //Blueio_Add(Buffer);
            //Blueio_Sync();
            //printf("temp: %3.2f, humidity: %3.2f, pressure: %3.2f, voltage: %d, current: %d\n", t, h, p, v, c);
            _waitms(10000);
    
            WIOE5_SetLowPower(0);
    
            Sleep(3600);
    
            WIOE5_SetLowPower(3);
        }
    }
    
    void StartMonitor()
    {
        int i;
    
        i = INA260_open(INACLK, INADTA);
        if (i =! 0x5449)
        {
            _pinh(57);
            while (1)
                Sleep(60);
        }
    
        INA260_configAveraging(7);
        INA260_configCurrent(7);
        INA260_configVoltage(7);
    }
    
    void StartBlue()
    {
        int i;
    
        i = Blueio_Init(BLURX, BLUTX);
        if (i != 352)
        {
            _pinh(56);
            _pinh(57);
            while (1)
                _waitms(500);
        }
    }
    
    void StartLora()
    {
        int i;
    
        i = WIOE5_Init(LRRX, LRTX);
        if (i != 4011)
        {
            _pinh(56);
            _pinh(57);
            while (1)
                _waitms(500);
        }
    
        // Set Point to Point Config
        WIOE5_SetLora(0);
    
    }
    
    void StartBME()
    {
        int rslt;
        float t, h, p;
    
        Bme = I2C_Init(BMECLK, BMEDTA, I2C_STD);
    
        dev_addr = BME68X_I2C_ADDR_HIGH;
        gas_sensor.variant_id = BME68X_VARIANT_GAS_LOW;
        gas_sensor.intf = BME68X_I2C_INTF;
        gas_sensor.read = BME68xRead;
        gas_sensor.write = BME68xWrite;
        gas_sensor.delay_us = BME68xWait;
        gas_sensor.amb_temp = 25;
        gas_sensor.intf_ptr = &dev_addr;
        rslt = bme68x_init(&gas_sensor);
        if (rslt != BME68X_OK)
        {
            _pinh(56);
            while (1)
                Sleep(60);
        }
    
        /* Set the temperature, pressure and humidity settings */
        conf.os_hum = BME68X_OS_16X;
        conf.os_pres = BME68X_OS_1X;
        conf.os_temp = BME68X_OS_2X;
        conf.filter = BME68X_FILTER_OFF;
        conf.odr = BME68X_ODR_NONE;
        rslt = bme68x_set_conf(&conf, &gas_sensor);
        if (rslt != BME68X_OK)
        {
            _pinh(56);
            while (1)
                Sleep(60);
        }
    
        /* Set the remaining gas sensor settings and link the heating profile */
        heatr_conf.enable = BME68X_ENABLE;
    
        /* Create a ramp heat waveform in 3 steps */
        heatr_conf.heatr_temp = 300; /* degree Celsius */
        heatr_conf.heatr_dur = 100; /* milliseconds */
        heatr_conf.heatr_temp_prof = NULL;
        heatr_conf.heatr_dur_prof = NULL;
        heatr_conf.profile_len = 0;
    
        rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &gas_sensor);
    
        /* kick off a reading */
        GetEnvironment(&t, &h, &p);
    }
    
    int GetEnvironment(float *temp, float *humidity, float *pressure)
    {
        int rslt;
        int delay;
        int f;
    
        rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &gas_sensor);
        delay = bme68x_get_meas_dur(BME68X_FORCED_MODE, &conf, &gas_sensor) + (heatr_conf.heatr_dur * 1000);
        gas_sensor.delay_us(delay, gas_sensor.intf_ptr);
        rslt = bme68x_get_data(BME68X_FORCED_MODE, &data, &f, &gas_sensor);
        *temp = data[0].temperature;
        *humidity = data[0].humidity;
        *pressure = data[0].pressure/3386.4;
    }
    
    uint8_t BME68xRead(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr)
    {
        int i;
    
        uint8_t dev_addr = *(uint8_t*)intf_ptr;
    
        i = I2C_In(Bme, dev_addr, reg_addr, 1, data, len);
    
        if (i > 0)
            return 0;
        else
            return -1;
    }
    
    uint8_t BME68xWrite(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr)
    {
        int i;
    
        uint8_t dev_addr = *(uint8_t*)intf_ptr;
    
        i = I2C_Out(Bme, dev_addr, reg_addr, 1, data, len);
    
        if (i > 0)
            return 0;
        else
            return -1;
    }
    
    void BME68xWait(uint32_t period, void *intf_ptr)
    {
        _waitus(period);
    }
    
    void Sleep(int t)
    {
        int i;
    
        i = _clkfreq;
       //Slow speed low power mode
        _clkset(_clkmode ^2, 20000);
    
        sleep(t);
    
        _clkset(_clkmode ^2, i);
    }
    
    void Hex(void *d)
    {
        char data[10];
        char *x = d;
    
        sprintf(data, "%2.2x%2.2x%2.2x%2.2x", x[0], x[1], x[2], x[3]);
        strcat(Buffer, data);
    }
    

    Bad new though, in my testing I found out that the Rev B board actually uses less power than the Rev C board in low power mode. Actually, it looks like it uses twice as much power as the Rev B. I went from 2ma to 5ma in low power mode.

    So far this is a point to point configuration and I waiting on the Gateway device.

    MIke

  • Finally got my point to point setup running. I ran out of P2's so I had to setup the receiver on a P1 instead. This led to coding changes as I moved the code over to the P1.

    Wasted a day trying to track down a strange behavior that turn out to be a code conversion hick-up. While I was trying to decode the message that I got from the environment sensors I realized that I didn't have a date attached to the data. At first thought is to add the date on the receiver side since passing the year, month, day, hours, minutes and seconds seem like a lot of data.

    I have a DS3231 clock module that I hooked up and used that to set the date on the P1. After looking at the code I realized that the date is just a long integer and could easily be passed along with the other data. So I moved the DS3231 to the sender side and added a small amount of code to send it along with the other data.

    I did run into an issue with receiving the data which is less than a quarter mile away. The documentation says that should not be an issue. I moved that antenna into a more upright position and that seems to be working now. I may have to change the Spreading Factor.

    The Spreading Factor changes the data rate at which the data is sent or basically the baud rate. The Spreading Factor ranges from 7 to 12 where 7 is 11k and 12 is 250 bits per second. I am using SF8 which is 3125 bits per second. Slowing down the bit rate can improve range as it leaves the signal in the air longer for the receiver to see it.

    Receiving program:

    #include "wioe5.h"
    #include "nextion.h"
    #include "simpletools.h"
    #include "fdserial.h"
    
    void DataOut(int, int, char*);
    
    #define LRX 6
    #define LTX 7
    #define NRX 15
    #define NTX 14
    #define DSCLK 0
    #define DSSDA 1
    
    time_t tm;
    char Buffer[256];
    float t, h, p;
    int v, c;
    
    int main()
    {
      int i;
    
      i = WIOE5_Init(LRX, LTX);
    
      if (i != 4011)
      {
        printi("Startup Error\n");
        while (1)
          pause(500);
      }
    
      i = Nextion_open(NRX, NTX, 9600);
    
      i = Nextion_color(0, 63, 0);
    
      WIOE5_SetLora(0);
    
      printi("Ready\n");    
    
      while(1)
      {
      WIOE5_GetLora(Buffer);
      Nextion_cls(0);
    
      memcpy(&t, &Buffer[0], 4);
      memcpy(&h, &Buffer[4], 4);
      memcpy(&p, &Buffer[8], 4);
      memcpy(&v, &Buffer[12], 4);
      memcpy(&c, &Buffer[16], 4);
      memcpy(&tm, &Buffer[20], 4);
    
      i = t;
      sprinti(Buffer, "Temperature: %d\n", i);
      DataOut(10, 10, Buffer);
    
      i = h;
      sprinti(Buffer, "Humidity: %d\n", i);
      DataOut(10, 40, Buffer);
    
      i = p*100;
      sprinti(Buffer, "Pressure: %d\n", i);
      DataOut(10, 70, Buffer);
    
      sprinti(Buffer, "Voltage: %d\n", v);
      DataOut(10, 100, Buffer);
    
      sprinti(Buffer, "Current: %d\n", c);
      DataOut(10, 130, Buffer);
    
      DataOut(10, 160, asctime(localtime(&tm)));
    
      }  
    }
    
    void DataOut(int x, int y, char *data)
    {
      int x2, y2;
      int i;
    
      i = strlen(data);
      x2 = i * 11;
      Nextion_xstr(x, y, x2, 30, 1, Nextion_color(0, 63, 0), 0, 0, 0, 3, data);
    }
    

    I need to add an SD card to the picture so that I can record the environment data in a permanent file. This may be a problem as the program is getting close to the 32k limit. Would not be an issue for the P2 though.

    Mike

  • I finally got the LoraWan gateway unit.

    Setting up the gateway and devices requires a Network Engineering degree.

    There are all these hex strings that need to be typed in and field values that don't match their names and passwords that are cryptic.

    Figuring out where the data goes so you can see if you're actually making a connection with the cloud.

    Join request that have to match your area, channels that match the gateway, encryption keys that match the cloud, and spread frequencies that match the data you want to send.

    After several days of working on this I got everything working with the driver I put together. One of the problems is that the device uses AT commands which are very wordy and writing code that has to visit all these return types to determine if what is happening is what the text says. Using AT commands on a command line is nice since you can see what you're doing but converting that to a driver not so much.

    Setting up the Blues Notecard is a breeze compared to this.

    Now I have to figure out how to use the API's so I can get the environment data out of the cloud.

    Mike

  • Here is the P1 version of the Lorawan environment sensor.

    All the code just fits in the P1 and uses very little power.

    40ma at 7volts when fully powered and only 12ma when in sleep mode. The WOIE5 unit is about 4ma when power and a few microamps when powered down. During transmitt it could be 100ma at 3.3v but only when sending the data.

    At 12ma it should run for a couple of weeks using two 18650 batteries.

    I'm using the Parallax Bosch BME680 board as the environment sensor using Bosches driver.

    P1 LoraWan

    Here is the code to make it work:

    //#define DEBUG
    
    #include "simpletools.h"
    #include "fdserial.h"
    #include <sys/time.h>
    #include "wioe5.h"
    #include "ina260.h"
    #include "bme68x.h"
    #include "DS3231.h"
    
    
    #define LED1 8
    #define LED2 9
    #define WRX 0
    #define WTX 1
    #define INACLK 15
    #define INADTA 14
    #define BMECLK 18
    #define BMEDTA 17
    #define DSCLK 6
    #define DSDTA 7
    
    uint8_t BME68xRead(uint8_t, uint8_t *, uint16_t len, void *);
    uint8_t BME68xWrite(uint8_t, uint8_t *, uint16_t len, void *);
    void BME68xWait(uint32_t, void *);
    void StartLora(void);
    void StartBME(void);
    void StartMonitor(void);
    void StartClock(void);
    int GetEnvironment(float *, float *, float *);
    void Sleep(int);
    void Hex(void *);
    
    
    char Buffer[256];
    time_t tm;
    i2c *Bme;
    char dev_addr;
    struct bme68x_dev gas_sensor;
    struct bme68x_conf conf;
    struct bme68x_heatr_conf heatr_conf;
    struct bme68x_data data[3];
    
    
    int main()
    {
      float t, h, p;
      int T, H, P;
      int v, c, l, i;
    
      StartMonitor();
      StartBME();
      StartLora();
      StartClock();
      l = 0;  
    
      i = 0;
      while (1)
      {
        GetEnvironment(&t, &h, &p);
        v = INA260_getVoltage();
        c = INA260_getCurrent();
        tm = DS3231_SetDateTime();
        Buffer[0] = 0;
        Hex(&t);
        Hex(&h);
        Hex(&p);
        Hex(&v);
        Hex(&c);
        Hex(&l);
        Hex(&tm);
        WIOE5_SendConfirmedHex(Buffer);
    #ifdef DEBUG
        T = t * 100;
        H = h * 100;
        P = p * 100;
        printi("%d %s\n", i++, Buffer);
        printi("Date: %d\n", tm);
        printi("temp: %d, humidity: %d, pressure: %d, voltage: %d, current: %d, light: %d\n", T, H, P, v, c, l);
    #endif
        pause(10000);
    
        WIOE5_SetLowPower(0);
    
        Sleep(3600);
    
        WIOE5_SetLowPower(3);
        }
    }
    
    void StartMonitor()
    {
      int i;
    
      i = INA260_open(INACLK, INADTA);
      if (i =! 0x5449)
      {
          high(LED1);
          while (1)
            Sleep(60);
      }
    
      INA260_configAveraging(7);
      INA260_configCurrent(7);
      INA260_configVoltage(7);
    }
    
    void StartLora()
    {
      int i;
    
      i = WIOE5_Init(WRX, WTX);
      if (i != 4011)
      {
        high(LED1);
        high(LED2);
        while (1)
          sleep(60);
      }
    
      WIOE5_Join();
    }
    
    void StartBME()
    {
      int8_t rslt = BME68X_OK;
      float t, h, p;
    
      Bme = i2c_newbus(BMECLK, BMEDTA, 0);
    
      dev_addr = BME68X_I2C_ADDR_HIGH;
      gas_sensor.variant_id = BME68X_VARIANT_GAS_LOW;
      gas_sensor.intf = BME68X_I2C_INTF;
      gas_sensor.read = BME68xRead;
      gas_sensor.write = BME68xWrite;
      gas_sensor.delay_us = BME68xWait;
      gas_sensor.amb_temp = 25;
      gas_sensor.intf_ptr = &dev_addr;
      rslt = bme68x_init(&gas_sensor);
      if (rslt != BME68X_OK)
      {
        high(LED2);
        while (1)
          Sleep(60);
      }
    
      /* Set the temperature, pressure and humidity settings */
      conf.os_hum = BME68X_OS_16X;
      conf.os_pres = BME68X_OS_1X;
      conf.os_temp = BME68X_OS_2X;
      conf.filter = BME68X_FILTER_OFF;
      conf.odr = BME68X_ODR_NONE;
      rslt = bme68x_set_conf(&conf, &gas_sensor);
      if (rslt != BME68X_OK)
      {
        high(LED1);
        while (1)
          Sleep(60);
      }
    
      /* Set the remaining gas sensor settings and link the heating profile */
      heatr_conf.enable = BME68X_ENABLE;
    
      /* Create a ramp heat waveform in 3 steps */
      heatr_conf.heatr_temp = 300; /* degree Celsius */
      heatr_conf.heatr_dur = 100; /* milliseconds */
      heatr_conf.heatr_temp_prof = NULL;
      heatr_conf.heatr_dur_prof = NULL;
      heatr_conf.profile_len = 0;
    
      rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &gas_sensor);
    
      /* kick off a reading */
      GetEnvironment(&t, &h, &p);
    }
    
    int GetEnvironment(float *temp, float *humidity, float *pressure)
    {
      int rslt;
      int delay;
      int f;
    
      rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &gas_sensor);
      delay = bme68x_get_meas_dur(BME68X_FORCED_MODE, &conf, &gas_sensor) + (heatr_conf.heatr_dur * 1000);
      gas_sensor.delay_us(delay, gas_sensor.intf_ptr);
      rslt = bme68x_get_data(BME68X_FORCED_MODE, &data, &f, &gas_sensor);
      *temp = data[0].temperature;
      *humidity = data[0].humidity;
      *pressure = data[0].pressure/3386.4;
    }
    
    uint8_t BME68xRead(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr)
    {
      int i;
    
      uint8_t dev_addr = *(uint8_t*)intf_ptr;
    
      i = i2c_in(Bme, dev_addr, reg_addr, 1, data, len);
    
      if (i > 0)
        return 0;
      else
        return -1;
    }
    
    uint8_t BME68xWrite(uint8_t reg_addr, uint8_t *data, uint16_t len, void *intf_ptr)
    {
        int i;
    
      uint8_t dev_addr = *(uint8_t*)intf_ptr;
    
      i = i2c_out(Bme, dev_addr, reg_addr, 1, data, len);
    
      if (i > 0)
        return 0;
      else
        return -1;
    }
    
    void BME68xWait(uint32_t period, void *intf_ptr)
    {
      usleep(period);
    }
    
    void StartClock()
    {
      DS3231_Open(DSCLK, DSDTA);
      DS3231_SetDateTime();
    }
    
    void Sleep(int t)
    {
      clkset(0x69, 20000);
      sleep(t);
      clkset(0x6f, 80000000);
    }
    
    void Hex(void *d)
    {
      char data[10];
      char *x = d;
    
      sprinti(data, "%2x%2x%2x%2x", x[0], x[1], x[2], x[3]);
      strcat(Buffer, data);
    }
    

    Mike

Sign In or Register to comment.