Willkommen bei bytebang » The blog about all and nothing » IoT parking sensor - LoRaWAN version

IoT parking sensor - LoRaWAN version

Jul 29 2019

The Problem

The parking sensor which was built in part one of this series was able to connect to a WiFi and transmit the data via MQTT with the software shown in part two of this series.

This is a very good approach if you want to test something in an early prototyping phase. If you want to roll out your devices then it is very unlikely that you will have a WiFi deployed wherever you sensor will be. So you have to look for another transmission technology for the last mile.

Here is a small compilation of available technologies:

https://www.researchgate.net/publication/329657897/figure/fig1/AS:703796623704065@1544809599498/Bandwidth-vs-Range-of-wireless-technology-4.ppm

The Solution

One simple solution is to use LoRa for transmission. Unfortunately with this approach you will need a receiver too - which is going to complicate things because we would have to built that one too.

LoRaWAN is (In opposite to LoRa) a complete network with a lot of of receivers which can be operated by companies or by the community. Like with mobile phone operators you will find some networks where you have to pay for transmission, but there are also community driven ones where transmission is free.

I will go with TheThingsNetwork - which is such a community driven LoRaWAN network with more than 7.000 gateways (=receivers) in ~140 countries all over the world. The next few paragraphs are walking you through the setup of this service.

Step 1: Create an account

This one is simple: Sign up at there registration page for an account. This will give you access to the console - the management interface for your IoT applications, gateways and any other TTN related things.

Step 2: Create an application

Before you can communicate with devices, you will need to add an application to the network and register devices to it. Multiple devices can transmit data to a single application. Here is how to create one:

add_application.png

After that the TTN creates an Application EUI for you (a kind of fingerprint of your application). For example: If you want to deploy a whole system of parking place monitors, and you want to have all the data managed at a single point then the application can be seen as "the container" for all your sensors / devices.

Step 3: Create devices

The next step is to create some devices for your application. I suggest that the device id should give a hint at hardware version and the location, and the EUI should be some kind of serial number.

register_device.png

Step 4: Connect your sensor to the network

The code which is needed therefore is simple

-- setup the display
gdisplay.attach(gdisplay.SSD1306_128_64, gdisplay.LANDSCAPE, false, 0x3c)
gdisplay.clear()
gdisplay.setfont(gdisplay.FONT_DEJAVU24)

-- READ THE SENSOR - GPIO13 (TRIG) and GPIO14 (ECHO)
s = sensor.attach("US015", pio.GPIO13, pio.GPIO15)
s:set("temperature", 25)

-- Set up LoRaWAN (make sure to use your own settings here)
lora.attach(lora.BAND868)
lora.setDevEui("0000000000000001")
lora.setAppEui("70B3D57ED001C2AD")
lora.setAppKey("47A450FEBDCE3A8FA2705714F4957428")
lora.join()

-- Function to execute
sendData = function()
       while true do
            gdisplay.clear()
            dist = 400
            try(function() dist = s:read("distance") end)
            gdisplay.write({gdisplay.CENTER,gdisplay.CENTER},tostring(dist))
            lora.tx(true, 1, pack.pack(dist))
            tmr.delayms(30 * 1000)
       end
end

-- Start a thread
th1 = thread.start(sendData)

After it sets up the display and initializes the sensor, it connects to the LoRaWAN network. This might take a few seconds, and it will fail if there is no gateway in the range of your receiver.

Finally it creates a function (sendData) which sends the current sensor reading every 30 seconds. In order to avoid freezing issues I have started this function in a separate thread.

This should give you first readings in your console:

lora_undecoded.png

Beside all the information about the receiving gateway(s), the needed airtime and other debug information we can see, that we are already receiving a payload: 01000000A142

Step 5: Give the received data some meaning

The final step is to give our data some meaning. All we see now in the console is a hex representation of the distance - which is not very convenient.

A quick look into the source code reveals that the lua function pack.pack() is a clever construct that converts any lua variable into a hex representation. So all we need is the inverse function to unpack the information.

TTN has a mechanism for that: It is called converter. It enables you to by writing javascript code to decode your hex payload into meaningful pieces. Here is an example how this works for plua's packed values:

function toNumber(bytes)
{
 var bits = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | (bytes[0]);
 var sign = ((bits >> 31) === 0) ? 1.0 : -1.0;
 var e = ((bits >> 23) & 0xff);
 var m = (e === 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;
 var f = sign * m * Math.pow(2, e - 150);

 return f;
}
 
function toInteger(bytes, len)
{
 var out = 0;
 for (var i=len-1; i>=0; i--)
  {
    out = (out << 8) + bytes[i];
  }
 return out;
}

function toString(bytes)
{
 var s = "";
 var i = 0;
 while (0 !== bytes[i])
  {
    s = s + String.fromCharCode(bytes[i]);
    i++;
  }
 return s;
}

function toBool(bytes)
{
 return (1 === bytes[0]);
}

function unpack(bytes)
{
 // array to hold values
 var data = [];

 // first byte holds the number of elements
 var size = bytes[0];

 // get data types
 var types = [];
 var count = 1;
 
 do
  {
   var type = bytes[count];
    types.push(type >> 4);
    types.push(type & 0x0F);
    count++;
  }while (types.length < size);
 
  types = types.slice(0, size);

 // decode data
 for (var i=0; i<size; i++)
  {
   var type = types[i];
   if (0 === type)
    {
      data.push(toNumber(bytes.slice(count,count+4)));
      count += 4;
    }
   else if (1 === type)
    {
      data.push(toInteger(bytes.slice(count,count+4), 4));
      count += 4;
    }
   else if (5 === type)
    {
      data.push(toInteger(bytes.slice(count,count+2), 2));
      count += 2;
    }
   else if (6 === type)
    {
      data.push(toInteger(bytes.slice(count,count+1), 1));
      count += 1;
    }
   else if (3 === type)
    {
      data.push(toBool(bytes.slice(count,count+1)));
      count += 1;
    }
   else if (4 === type)
    {
     var s = toString(bytes.slice(count));
      data.push(s);
      count += (s.length + 1);
    }
  }
  
 return data;
}

// ----------------------------------------------------

function Decoder(bytes, port) {

 var decoded = {};
  
 // Data from LuaRTOS
 if (port == 1) {
   var data = unpack(bytes);
    decoded.distance = data[0];
  }
  
 return decoded;
}

TTN calls the function Decoder and hands the hex string and the port where the data was received over as parameters. You can return an object with the decoded values on it. All functions above the Deocder function are needed do decode lua's packed values.

LuaRTOS version of pack() is the same approach as the Chayenne LPP protocol mentioned in the TTN documentation - with the difference that there is no such decoder included in TTN. 

Now we can see the values in clear text (which is a good thing when it comes to data processing of the values - which will be content of the next article.

sent_decoded_list.png

And this is the detail view of one of the messages:

sent_decoded.png

Happy hacking !

Get Social


(c) 2024, by bytebang e.U. - Impressum - Datenschutz / Nutzungsbedingungen
-