How to monitor a beehive with Arduino Nano 33BLE (bluetooth)

You have a beehive and you want to optimise your interventions ? Checkout our project where we embed sensors in the beehive.

projectImage

Things used in this project

 

Hardware components

HARDWARE LIST
2 DFRobot DHT22 Temperature and Humidity Sensor
1 Arduino Nano 33 BLE Sense
1 Breakout board Sigfox
1 Hackeet Accu Li-Ion 3,7 V 1050 mAh
1 Seeed LiPo Rider Pro Board
2 Adafruit Waterproof DS18B20 Digital temperature sensor
1 Weight sensor H40A-C3-0150
1 Seeed Load Cell Amplifier - HX711
1 Seeed Module GPS Grove
1 Adafruit Light sensor VEML7700
1 Box G218C
1 Solar panel SOL2W
10 Grove Right Connector
1 Seeed Kit 5 Grove cables - 4 female contacts
1 Interruptor DSRR2B
1 LDR, 5 Mohm

Software apps and online services

 

Arduino IDE

 

KiCad

 

Android Studio

 

Microsoft Azure

 

Ubidots

Hand tools and fabrication machines

 

Soldering Iron Kit, Weller XNT/THM Tips

 

Hot glue gun (generic)

 

Story

 

Introduction

 

Our team was led to rub shoulders with beekeepers and after few exchanges some issues appeared. Indeed, beekeepers do not always have the time to check their beehives or the weather might nt be suitable (heavy rain, strong wind) for checking.

 

Thus, they asked us if there is a way to remotely monitor their beehives. They gave us the technical specifications we would have to meet and we started to design a solution for them.

 

So, our solution lie in a monitored beehive with an Arduino Nano. We linked to this board several sensors mesuring :

-the temperature inside and outside the beehive

 

-the beehive's weight

 

-the battery of the system

 

-the humidity inside and outside the beehive

 

These data are then uploaded to Ubidots, a cloud interface allowing beekeepers to consult the data and the state of the beehive.

 

When near the beehive, they can also connect to the board thanks to a bluetooth android application.

 

 

 

First step : data acquisition

 

Of course when it comes to environment monitoring, the first thing that comes to mind is data acquisition. Which data do we plan to measure ? How do we measure it ? How precisely ?

 

So based on the specifications we started to make a component list with everything we will need. We made sure to select well known components with great community support for easier and faster problem solving.

 

For starters, for the brain of the operation we turned to an Arduino Nano 33 BLE microcontroller which provides the best software support, with easy programming and a lot of libraries, as well as great features like Bluetooth Low Energy and a low battery consumption.

projectImage

Then for the actual sensors we went with a DHT22 for temperature and humidity reading outside the beehive, but also a DHT11 for temperature and humidity reading inside the beehive. Both are very similar, the DHT22 just being an upgraded version of the DHT11 with a wider range and precision. In our case we initially planned to use two DHT22 but we could only get one due to it being out of stock so we substituted the missing DHT22 with a DHT11.

 

We also choose two DS18B20 as our temperature probes, simply because they fit our needs and are really convenient to put in a beehive.

projectImage
projectImage
projectImage

Next, for the weight we were provided with a load cell so we simply had to pick a load cell amplifier and ADC, like the HX711 Grove form Seeed.

projectImage
projectImage

Finally, for light sensing, we choose a simple and affordable solution. We used a photoresistor along with a voltage divider. For the battery, we just used another voltage divider, as it's voltage can go up to 4.1V whereas the Arduino can only take inputs up to 3.3V.

Breadboard testing

 

We started testing every sensor individually. We downloaded libraries made by the Arduino community from each sensors and simply used them to print the sensor output on the serial console.This part was relatively easy due to most libraries being very user friendly. The most complicated part was working with the weight sensor as it requires some calibration.

 

 

Bluetooth Low Energy : a poorly documented maze

 

Then we began working on the BLE, which would allow us to monitor and test the sensors wirelessly from a phone. Working with BLE using the Arduino BLE library was fairly easy, as BLE is not so complicated to understand. You've got services which regroups characteristics and each characteristics can have one or more descriptors. If you want to send a data, you create a characteristic, which will hold the actual value for that data, and you assign a descriptor to the characteristic which will tell the remote device how the data is supposed to be interpreted. Then you assign all your characteristics to a service.

 

That looks easy on paper but when you want to do things correctly, it get more complex. The thing is that if you know how you send the data and use a custom app to retrieve it, you don't have to worry about which format you're sending the data. But if you actually want to follow the BLE standard, you will have to find how UUIDs works, every UUID has a meaning.

 

Services have an UUID to tell which type of service it is and characteristics have an UUID to tell which type of data it is (temperature, humidity, voltage, power, etc). All the information on which UUID to use can be found here, with Gatt Service listing all the standard services UUIDs and Gatt Characteristics listing all the standard characteristics UUIDs.

 

Finally, the descriptor's value is used to tell the device receiving the data, how it should interpret the characteristic's data (what is the unit ? is there an exponent ? where is the sensor located ?). Every characteristic has it's own descriptor value and the documentation for this value is not available for every characteristic. We have to search through archives and Github pages to find old documentation about the value of rarely used characteristics like light sensing.

 

To save you some time, you can find all the standard services and characteristics UUIDs in the BLE documentation here and there. You can also find most of the descriptors value in this Github repository or by search for there name "org.bluetooth.chracteristic.XXX" on Google (in fact the official documentation XMLs are still hosted on the Bluetooth official website, just not linked anywhere).

 

 

BLE is finally here

 

After a lot of trouble, we finally made it happen and had a service with a correct name, showing up characteristics with correct name and units ! Let's see how me did it using the Arduino BLE library. We will not go through every single step, like enabling BLE or starting to advertise but only the most complex part, creating the service, characteristics and descriptors.

 

First, we start by making a service :

CODE
BLEService BLEServiceUBeeZ("181A");

Here, the UUID 181A means that it is an "Environmental Sensing" service (as seen here on page 21).

 

Then we create our characteristics with their descriptor, let's take for example a temperature characteristic.

 

We create the characteristic :

CODE
BLEShortCharacteristic BLECharacteristicTemperature = BLEShortCharacteristic("00002A6E-0000-1000-8000-00805F9B34FB", BLERead | BLENotify)

It's a Short characteristic meaning that the data will be on 2 bytes, we will see why we choose that later.

 

Then, the UUID can be looked at like the combinaison of a "base" 128bit UUID : 0000xxxx-0000-1000-8000-00805F9B34FB, with the xxxx being a standard 16 bit UUID.Here the 2A6E refers to a temperature characteristic (as seen here on page 14).Finally, we have the BLERead | BLENotify which indicates that the data can only be read by a remote device and that this device will be notified when the data value changes.

 

Now, we have to create the descriptor for the temperature, with its value.

CODE
uint8_t descriptorTemperature[7] = {0x0E, (uint8_t)(int8_t)-2, 0x2F, 0x27, 0x01, 0x01, 0x00};
BLEDescriptor BLEDescriptorTemperature("2904", descriptorTemperature, 7);

The descriptor value is always 7 bytes so we create a 7 bytes array that we fill according to the documentation. We will also need the standard temperature descriptor values, which we can find here.

 

According to the second link, we know that the temperature should be sent as a 16 bit signed integer (that why we choose the BLEShortCharacteristic, because its 16 bits), in degrees Celsius and with a -2 exponent (meaning that if the temperature is 12.34 °C, we send 1234).

 

So for our 7 byte array, we have :

 

-0x0E : signed 16-bit integer

 

-(uint8_t)(int8_t)-2 : decimal exponent -2

 

-0x2F 0x27 : when we invert them (as the bytes are not sent in the same endian as the documentation was written in), we get 0x272F which corresponds to degrees Celsius (as seen in the documentation, page 23)

 

-0x01 : means that we use the Bluetooth SIG Assigned Numbers standard

 

-0x01 0x00 : when we invert them (for the same reason as before), we get 0x0001 which corresponds to "first", indicating that this is the first temperature sensor (as seen in the other documentation, page 3)

 

Finally, we assign everything, starting with the descriptor to the characteristic

CODE
BLECharacteristicTemperature.addDescriptor(BLEDescriptorTemperature);

Then we assign the characteristic to the service :

CODE
BLEServiceUBeeZ.addCharacteristic(BLECharacteristicTemperature);

Later, when we want to actually send the data, we simply call :

CODE
BLECharacteristicTemperature.writeValue(temperature*100);

Not forgetting the x100 as the data is sent with a -2 exponent.

 

We repeat this process for every sensor, each with its own characteristic and descriptor, and assign every characteristic to the same service.

Sensors testing over the air

 

When the BLE part was done, we hocked up every sensor and made a program combining all our simple individual testing of the sensors to make one big program which regrouped all sensors and sent their data over BLE.

 

We downloaded on our phone an app called nRF Connect which allows us to connect to BLE devices and see all their services, characteristics and descriptors.

 

We used it to connect to the Arduino and check that everything was fine :

projectImage
projectImage
projectImage
projectImage

We had the result that we wanted with all sensors showing their data in the correct format and with the correct values.

 

Furthermore, we spent some time putting all our code in order, splitting it in different files for easier debugging and we also spent a lot a time putting all our Bluetooth code on a separated thread. By doing so, we made sure that the BLE part or the program would not interfere with the rest of the program in any way.

 

We used RTOS features, as the Arduino nano 33 BLE runs Mbed OS, like Thread or EventQueue, feel free to check out the examples.

 

 

Getting the data from your home

 

Enough with Bluetooth, as you know its range is only a few meters and the main goal of the project was to send the data to the internet so they can be accessible from anywhere in the world, like from your home.

 

To send the data to the internet, we were provided with a small LPWAN module. It allows to send 12 bytes of data every 10 minutes (at least) from almost anywhere in France and using little power. It communicates with the Sigfox network, which is a cellular like network, and thus you don't even need Wifi near the hive !

 

The module in question is a small board, the Sigfox Breakout board BRKWS01. It communicates using simple AT commands (documented here) over a serial connection with the Arduino.

projectImage

This solution was great, the only major disadvantage being the length of the payload being quite limited. To overcome this problem, we had to think about how we would send the data, how we would reduce its size, as one simple integer already takes 4 bytes.

 

 

Encoding the data for transmission

 

To encode the data, we wondered what range of values did we need for each sensor and at which precision. For example, we wanted the temperature to be sent with a 0.1 °C precision but we only wanted a precision of 2% for the humidity. We also need to be able to send a negative temperature (as it gets pretty cold in France). So we came up with an Excel sheet containing the whole payload format.

projectImage
projectImage

A little note about GPS : in the beginning of the project we intended to use a GPS module to locate where the hive is. It didn't quite make it into the final project as we ran out of time but when we create the payload encoding model we thought of it and realized that a single payload was not enough to fit all the data (latitude and longitude both takes 19 bits to be precise on a map).

 

So what we did, was set the last bit of the payload to be an indicator or what type of payload is it, sensors or GPS. As you can see in the first picture above, the sensor payload has its last bit set to 0. We also have a payload with the last bit set to 1 which represent the GPS payload :

 

projectImage
projectImage

Finally, we changed the Arduino program so that it can send data every 10 minutes, encoding it according to the tables above and sending them with the Sigfox module.

 

Every message of 12 bytes is received and can be seen on the Sigfox backend website. We can create callbacks when a message is received to pass along the data to another third party.

 

The data's trip

 

We wanted to display the data on a nice, user friendly dashboard and we found that Ubidots would be a great fit for out project. The thing is that our data is still encoded and Ubidots only accept data in a raw format, moreover we need to decode the data differently depend on the last bit of the payload (depending if it's a sensors payload or a GPS payload) which is not possible to do directly on Ubidots. So we couldn't simply tell the Sigfox backend to send the data directly to Ubidots with a callback.

 

The solution we found was that, we can send the data from the Sigfox backend to an intermediate who will do the decoding, and then the intermediate will send the data to Ubidots. The intermediate we used was Microsoft Azure, we would have a diagram like this :

projectImage

We choose Microsoft Azure for it's Azure Functions feature and because it was free for students.

Decoding the data on Azure

 

First, we're going to assume that you have a few notions of how HTTP requests work. So to put it simple, a callback on the Sigfox backend is just making an HTTP request to an URL. You can pass data in the body of the request, for example in form of a JSON with a key containing the 12 bytes payload in hexadecimal.

 

Next, Azure Functions is a service that allows you to run code, in a few languages, when receiving an HTTP request on an endpoint. You can get the query parameters or the body content of the request as a function parameter in the code that will run. You can of course make HTTP request from this code, maybe you're starting to see the picture.

 

So we created a callback on the Sigfox backend which will make an HTTP request every time a payload is received from the Breakout module. This HTTP request will contain the message received by the Sigfox backend in its body and will trigger a code runing on Azure Functions. The code on Azure will then decode the payload bytes and put each sensors actual value in a JSON, with each sensor having its key. Finally, the JSON is sent as the body of an HTTP request to Ubidots.

 

 

 

Displaying the data on Ubidots

Once all the data was arriving correctly on Ubidots, we customized the dashboard and made it pretty and user friendly. All the data was displaying correctly so we were done with this part.

projectImage

Optimizing effeciency

 

As we were getting done with our overall design, we started to focus on the energy effeciency of the system, as it was going to run on a LiPo battery and a solar panel.

projectImage
projectImage
projectImage

We started with the battery being connected to the LiPo Rider Pro and the Arduino being connected to the 5V USB output of the LiPo Rider Pro. This was easy and convenient but really not energy effecient. Actually, a LiPo battery voltage is between 4.1V when charged, to 3V when discharged. This means that the LiPo Rider Pro has to step up the voltage to 5V. Then the Arduino, which runs on 3.3V, has to step down the voltage from 5V to 3.3V, losing even more power in the process.

 

This wasn't a viable option so we decided to keep the LiPo Rider Pro for its charging management feature with the solar panel, but decided to not use its output. Instead we decided that we would power everything with 3.3V.

We started by cuting a trace on the Arduino to disable its internal 5V to 3.3V regulator and to be able to power it directly with a 3.3V input, as mentioned in the documentation, page 2). All our other sensors were meant to used 3.3V so we didn't have to make any changes there.

 

Next, we connected the battery output to a 3.3V regulator's input and connected our arduino and sensors to the regulator's output. Once we've done that, we measured a consumption 10 times lower that before.

 

Finally, once we were done with hardware power optimisations, we turned to software. A few sensors had sleep modes availables so we simply used them and got to a consumption in idle state as low as 1.23 mA. During data transfert, we still got around 28.5mA but this happens every 10 minutes for 12 seconds so we were quite happy with what we got as according to our calculations, the battery could operate the system for 23 days without being charged.

projectImage
projectImage
projectImage

Of course the system will be charged by the solar panel, but having a low consumption means that the system will be able to operate flawlessly even during long cloudy periods or during the winter when there is not much sun.

 

We tested our system over a week and got provising results only dropping 0.22 volts (the voltage drops faster when the battery is fully charged or is fully discharged).

projectImage

Finalising the prototype : creating the PCB

 

Once the project was done and completed on the breadboard, it was time to make it more robust, we made a PCB to connected everything.

 

We designed the PCB using KiCad, started by placing every component we needed. We even replaced our 3.3V regulator with a more compact and power effecient 3.3V SMD LDO. Every sensor will be connected with a Grove header, to make it more simple and more durable. We also used a jumper that will later be connected to a physical switch to turn on or off the system.

 

Once the components were placed, we connected them properly and started make the layout of the actual PCB. We placed every Grove header, every SMD components and made sure to have large margins. Once everything was placed we were ready to send the gerber files to print the PCB.

projectImage
projectImage
projectImage

We received our PCB and soldered every header and components on it, then tested it. Everything was working just like on the breadboard, so we decided to go to the next step.

Packing up everything watertightly

 

Now that the system is ready, we needed to protect it agaist the difficult environnement it will be facing. We got outself a water resistant box where we will put the system.

projectImage

We drilled a few holes in the box for the sensors and even used some water resistant connectors for the weight sensor and the solar block that allowed us to completely disconnect them from the box, for easier transportation. The holes were covered in hot glue to make it as much water resistant as prossible.

 

Once everything was done, we ended us with a very provising looking system :

projectImage
projectImage
projectImage
projectImage
projectImage
projectImage

Final test in the field

 

Finally, our system was ready for prime time. So one afternoon, we went to an apiary and installed our system. It was a really great experience, we learned a lot talking with them and we sucessfully put our box under a hive with the weight sensor and temperatures and humidity sensors in the hive, without forgetting the solar block on top.

projectImage
projectImage
projectImage

We let the system run for 2 weeks during winter and checked frequently the Ubidots dashboard to see how the bees where doing.

Final results

 

After all this time, our system what still working perfectly fine. Every 10 minutes for 2 weeks the sensors data was sent and we got amazing results.

projectImage
projectImage
projectImage
projectImage
projectImage

Every sensors has worked well, our battery was almost always fully charged, never dropping bellow 80%. We can now proubly say that this project was a sucess !

Bonus : Android App

 

Even if the data was visible from the nRF Connect app, it was not really user friendly, so that's why we decided to create an Android application. It allows the user to see if every sensors is connected and working properly and display their values in a simple and pretty way.

 

The Android app uses standard API to acess the BLE functionnality of the Android device. By following the simple examples, we were able to come up with an app that we are quite happy with.

projectImage
projectImage
projectImage
projectImage
projectImage

Custom parts and enclosures

 

icon pcb-kicad_5hXGCjbacV.zip 344KB Download(1)

Schematics

 

Circuit schematic

projectImage

Pinout

icon pinout_-_feuille_1_ak0Nu4BMV1.zip 186KB Download(0)

Code

Main

Arduino

It's the main program to launc

CODE
/*
 * Bibliothques utilises :
 * - ArduinoBLE by Arduino
 * - Adafruit Unified Sensor by Adafruit
 * - DHT sensor library by Adafruit
 * - HX711 Arduino library by Bogdan Necula
 * - MaximWire by xeno
 * - NanoBLEFlashPrefs by Dirk
 */
 
#include "UBeeZ_LED.h"
#include "UBeez_Bluetooth.h"
#include "UBeeZ_Temperature_humidity_sensor.h"
#include "UBeeZ_Sigfox.h"
#include "UBeeZ_Battery_weight_light.h"
#include "UBeeZ_Prefs.h"
#include "UBeeZ_GPS.h"

#define DISABLE_SIGFOX false
#define DISABLE_GPS false

using namespace std;

std::chrono::time_point<std::chrono::system_clock> last_gps_sync = std::chrono::system_clock::now()-std::chrono::seconds(getPrefs()->gpsSyncDelay);

void setup() {
  // put your setup code here, to run once:
  set_time(0);
  SerialUSB.begin(9600);
  digitalWrite(PIN_ENABLE_I2C_PULLUP, LOW);
  digitalWrite(PIN_ENABLE_SENSORS_3V3, LOW);

  setupLedRGB();
  setupPrefs();
  if(!DISABLE_SIGFOX){
    setupSigfox();
  }
  setupTemperatureHumidity();
  setupBatteryLight();
  setupWeight();
  if(!DISABLE_GPS){
    setupGPS();
  }
  
  setupBLE();
  if(BLE_ALWAYS_ACTIVE){
    startBLE();
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  if(!DISABLE_SIGFOX){
    sendPayloadCapteurs();
  }
  if(!DISABLE_GPS && std::chrono::system_clock::now() >= last_gps_sync + std::chrono::seconds(getPrefs()->gpsSyncDelay)){
      if(get_GPS_location()){
        sendPayloadGPS();
      }
      last_gps_sync = std::chrono::system_clock::now();
    }
    rtos::ThisThread::sleep_for(std::chrono::seconds(getPrefs()->sigfoxSyncDelay)); 
}

UBeeZ_Prefs.cpp

C/C++

CODE
#include "UBeeZ_Prefs.h"

NanoBLEFlashPrefs prefsManager;
FlashPrefs prefs;

void setupPrefs(){
  int rc = prefsManager.readPrefs(&prefs, sizeof(prefs));
  if(rc == FDS_SUCCESS){
    if(DEBUG_PREFS){
      SerialUSB.println("Preferences found !");
    }
  }
  else{
    if(DEBUG_PREFS){
      SerialUSB.print("No preferences found. Return code: ");
      SerialUSB.print(rc);
      SerialUSB.print(", ");
      SerialUSB.println(prefsManager.errorString(rc));
    }
  }

  if(DEBUG_PREFS){
    SerialUSB.println("Preferences values :");
    SerialUSB.print("sigfoxSyncDelay : ");
    SerialUSB.println(prefs.sigfoxSyncDelay);
    SerialUSB.print("gpsLatitude : ");
    SerialUSB.println(prefs.gpsLatitude);
    SerialUSB.print("gpsLongitude : ");
    SerialUSB.println(prefs.gpsLongitude);
    SerialUSB.print("gpsHeading : ");
    SerialUSB.println(prefs.gpsHeading);
  }
}

FlashPrefs* getPrefs(){
  return &prefs;
}

void writePrefs(){
  if(DEBUG_PREFS){
    SerialUSB.println("Writting preferences values :");
    SerialUSB.print("sigfoxSyncDelay : ");
    SerialUSB.println(prefs.sigfoxSyncDelay);
    SerialUSB.print("gpsLatitude : ");
    SerialUSB.println(prefs.gpsLatitude, 6);
    SerialUSB.print("gpsLongitude : ");
    SerialUSB.println(prefs.gpsLongitude, 6);
    SerialUSB.print("gpsHeading : ");
    SerialUSB.println(prefs.gpsHeading);
  }
  
  prefsManager.writePrefs(&prefs, sizeof(prefs));
  // Wait until completion
  while (!prefsManager.operationCompleted()) {
    rtos::ThisThread::sleep_for(std::chrono::milliseconds(200));
  }
}

UBeeZ_Prefs.h

C Header File

CODE
#ifndef UBEEZ_PREFS
#define UBEEZ_PREFS

#include <NanoBLEFlashPrefs.h>
#include <Arduino.h>
#include <Serial.h>
#include "rtos.h"

#define DEBUG_PREFS true

typedef struct FlashStruct {
  unsigned short sigfoxSyncDelay = 10*60;
  unsigned int gpsSyncDelay = 24 * 60 * 60; //Tous les jours
  double gpsLatitude = 0;
  double gpsLongitude = 0;
  float gpsHeading = 0;
} FlashPrefs;

void setupPrefs();
FlashPrefs* getPrefs();
void writePrefs();

#endif

UBeeZ_Sigfox.cpp

C/C++

CODE
#include "UBeeZ_Sigfox.h"
#include "UBeeZ_LED.h"
#include "UBeeZ_Temperature_humidity_sensor.h"
#include "UBeeZ_Battery_weight_light.h"
#include "UBeeZ_Prefs.h"
#include <Serial.h>
#include "mbed.h"
#include "rtos.h"

void setupSigfox(){
  pinMode(PIN_SIGFOX_RESET, OUTPUT);
  Serial1.begin(9600);
  digitalWrite(PIN_SIGFOX_RESET, HIGH);
}

void sendPayloadCapteurs(){

  //Resume Sigfox
  digitalWrite(PIN_SIGFOX_RESET, LOW);  
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_SIGFOX_RESET, HIGH);
  
  led_blink_R(100); 
  while(!Serial1.available()){
    Serial1.write("AT\r\n");
    rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  }
  led_stop_R();
  digitalWrite(LEDR, HIGH);
  
  //SerialUSB.println("Sending Sigfox data");
  /* Payload :
   * Humidit : 6 bit
   * Temprature : 10 bit
   * Poid : 10 bit
   * Batterie : 8 bit
   */

  //SerialUSB.println("Valeurs converties :\n");

  updateTemperatureHumidity();
  //Range humidit : 0 - 100 ===>>> 0 - 64
  unsigned int convertedHumidite[2];
  float currentHumidity;
  for(int i=0;i<2;i++){
    currentHumidity = getHumiditeSensor(i);
    if(isnan(currentHumidity)){
      convertedHumidite[i] = 63;    //Humidity reading error
    }
    else if(currentHumidity < 0 || currentHumidity > 100){
      convertedHumidite[i] = 62;    //Humidity out of range
    }
    else{
      convertedHumidite[i] =  std::round(currentHumidity*60/100);
    }
    //SerialUSB.println("Humidite %d\t: %u\t%#03x\n", i, convertedHumidite[i], convertedHumidite[i]);
  }

  //Range temprature : -20.0 - 80.0 ===>>> 0 - 1000
  unsigned int convertedTemperature[4];
  //char strTemp[255];
  float currentTemp;
  for(int i=0;i<4;i++){
    currentTemp = getTemperatureSensor(i);
    if(isnan(currentTemp)){
      convertedTemperature[i] = 1023;   //Temperature reading error
    }
    else if(currentTemp>80 || currentTemp < -20){
      convertedTemperature[i] = 1022;   //Temperature out of range
    }
    else{
      convertedTemperature[i] = std::round((currentTemp+20)*10);
    }
    //sprintf(strTemp, "Temperature %d\t: %f=>%u\t%#05x\n", i, getTemperatureSensor(i), convertedTemperature[i], convertedTemperature[i]);
    //SerialUSB.print(strTemp);
  }

  //Range poid : 0.0 - 100.0 ===>>> 0 - 1000
  unsigned int convertedPoid;
  if(!updateWeight()){
    convertedPoid = 1023;   //Weight reading error
  }
  else if(getWeight() < 0){
    if(getWeight() > -0.5){
      convertedPoid = 0;
    }
    else{
      convertedPoid = 1022;
    }
  }
  else if(getWeight() > 100){
    convertedPoid = 1022;   //Weight out of range
  }
  else{
    convertedPoid = std::round(getWeight()*10);
  }
  //SerialUSB.println("Poid \t\t: %u\t%#05x\n", convertedPoid, convertedPoid);

  //Range batterie : 0 - 5 ===>>> 0 - 500
  unsigned int convertedBatterie;
  float batteryVoltage = getBatteryVoltage();
  if(batteryVoltage > 5 || batteryVoltage < 0){
    convertedBatterie = 511;
  }
  else{
    convertedBatterie = std::round(batteryVoltage*100);
  }
  /*char str[256];
  sprintf(str, "Batterie \t: %u\t%#04x\n\n", convertedBatterie, convertedBatterie);
  SerialUSB.print(str);*/

  //Range luminosite : 0-1000 ===>>> 0 - 1000
  unsigned int convertedLuminosite = getLight();
  if(convertedLuminosite > 1023){
    convertedLuminosite = 1023;
  }

  unsigned char payload[12];
  for(int i=0;i<12;i++){
    payload[i] = 0;
  }
  payload[0] = convertedHumidite[0] << 2 | convertedHumidite[1] >> 4;
  payload[1] = convertedHumidite[1] << 4 | convertedTemperature[0] >> 6;
  payload[2] = convertedTemperature[0] << 2 | convertedTemperature[1] >> 8;
  payload[3] = convertedTemperature[1] & 0xFF;
  payload[4] = convertedTemperature[2] >> 2;
  payload[5] = convertedTemperature[2] << 6 | convertedTemperature[3] >> 4;
  payload[6] = convertedTemperature[3] << 4 | convertedPoid >> 6;
  payload[7] = convertedPoid << 2 | convertedBatterie >> 7;
  payload[8] = convertedBatterie << 1 | convertedLuminosite >> 9;
  payload[9] = convertedLuminosite >> 1;
  payload[10] = convertedLuminosite << 7;

  //SerialUSB.print("AT$SF=");
  
  Serial1.print("AT$SF=");
  char strBuffer[3];
  for(int i=0;i<12;i++){
    sprintf(strBuffer, "%02X", payload[i]);
    //SerialUSB.print(StrBuffer);
    Serial1.print(strBuffer);
  }
  
  //SerialUSB.print("\r\n");
  Serial1.print("\r\n");
  rtos::ThisThread::sleep_for(std::chrono::seconds(10));
  Serial1.print("AT$P=2\r\n");
}

void sendPayloadGPS(){
  //Resume Sigfox
  digitalWrite(PIN_SIGFOX_RESET, LOW);  
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_SIGFOX_RESET, HIGH);
  
  led_blink_R(100); 
  while(!Serial1.available()){
    Serial1.write("AT\r\n");
    rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  }
  led_stop_R();
  digitalWrite(LEDR, HIGH);

  unsigned int convertedLatitude = std::round((getPrefs()->gpsLatitude+90)*1000);
  //printf("Latitude \t: %u\t%#04x\n", convertedLatitude, convertedLatitude);

  unsigned int convertedLongitude = std::round((getPrefs()->gpsLongitude+180)*1000);
  //printf("Longitude \t: %u\t%#04x\n", convertedLongitude, convertedLongitude);

  unsigned int convertedOrientation = std::round(getPrefs()->gpsHeading);
  //printf("Orientation \t: %u\t%#04x\n\n", convertedOrientation, convertedOrientation);

  unsigned char payload[12];
  for(int i=0;i<12;i++){
    payload[i] = 0;
  }

  payload[0] = convertedLatitude >> 11;
  payload[1] = convertedLatitude >> 3;
  payload[2] = convertedLatitude << 5 | convertedLongitude >> 14;
  payload[3] = convertedLongitude >> 6;
  payload[4] = convertedLongitude << 2 | convertedOrientation >> 7;
  payload[5] = convertedOrientation << 1;

  payload[11] |= 0x1;

  //Sending data
  Serial1.print("AT$SF=");
  char strBuffer[3];
  for(int i=0;i<12;i++){
    sprintf(strBuffer, "%02X", payload[i]);
    //SerialUSB.print(StrBuffer);
    Serial1.print(strBuffer);
  }

  //SerialUSB.print("\r\n");
  Serial1.print("\r\n");
  rtos::ThisThread::sleep_for(std::chrono::seconds(10));
  Serial1.print("AT$P=2\r\n");
}

UBeeZ_Sigfox.h

C Header File

CODE
#ifndef UBEEZ_SIGFOX
#define UBEEZ_SIGFOX

#define PIN_SIGFOX_RESET 5

void setupSigfox();
void sendPayloadCapteurs();
void sendPayloadGPS();

#endif

UBeeZ_Temperature_humidity_sensor.cpp

C/C++

CODE
#include "UBeeZ_Temperature_humidity_sensor.h"
#include <DHT.h>
#include <MaximWire.h>
#include "rtos.h"

MaximWire::Bus temperature_bus(PIN_TEMPERATURE_BUS);
MaximWire::DS18B20 temperature_device_1(ADDR_TEMPERATURE_1);
MaximWire::DS18B20 temperature_device_2(ADDR_TEMPERATURE_2);

DHT dht22_1(DHT22_1_PIN, DHT11);
DHT dht22_2(DHT22_2_PIN, DHT22);

float temperatures[4] = {NAN, NAN, NAN, NAN};
float humidites[2] = {NAN, NAN};

void setupTemperatureHumidity(){
    dht22_1.begin();
    dht22_2.begin();
}

void updateTemperatureHumidity(){
  temperature_device_1.Update(temperature_bus);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(100));
  temperatures[1] = temperature_device_1.GetTemperature<float>(temperature_bus);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(100));
  temperature_device_2.Update(temperature_bus);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(100));
  temperatures[2] = temperature_device_2.GetTemperature<float>(temperature_bus);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(100));

  temperatures[0] = dht22_1.readTemperature();
  temperatures[3] = dht22_2.readTemperature();

  humidites[0] = dht22_1.readHumidity();
  humidites[1] = dht22_2.readHumidity();
}

float getTemperatureSensor(int index){  //Retourne temprature en C
  if(index>=0 && index <4){
    return temperatures[index];
  }
  return NAN;
}

float getHumiditeSensor(int index){ //Retourne humidit entre 0 et 100
  if(index>=0 && index <2){
    return humidites[index];
  }
  return NAN;
}

UBeeZ_Temperature_humidity_sensor.h

C Header File

CODE
#ifndef UBEEZ_TEMPERATURE_HUMIDITY_SENSOR
#define UBEEZ_TEMPERATURE_HUMIDITY_SENSOR

#define MAXIMWIRE_EXTERNAL_PULLUP

#define ADDR_TEMPERATURE_1 "280C8016A8013C93"
#define ADDR_TEMPERATURE_2 "2876BF56B5013C95"
#define PIN_TEMPERATURE_BUS 10

#define DHT22_1_PIN 9
#define DHT22_2_PIN 8


void setupTemperatureHumidity();
void updateTemperatureHumidity();

float getTemperatureSensor(int index);
float getHumiditeSensor(int index);

#endif

UBeeZ_Battery_weight_light.cpp

C/C++

CODE
#include "UBeeZ_Battery_weight_light.h"
#include "HX711.h"
#include <Arduino.h>
#include "rtos.h"

HX711 weightSensor;
double weight = NAN;

void setupBatteryLight(){
  pinMode(PIN_BATTERY, INPUT);
  pinMode(PIN_LIGHT_ENABLE, OUTPUT);
  pinMode(PIN_LIGHT_READ, INPUT);
  digitalWrite(PIN_LIGHT_ENABLE, LOW);
}

void setupWeight(){
  weightSensor.begin(PIN_WEIGHT_DOUT, PIN_WEIGHT_PSCK);
  weightSensor.power_down();
}

float getBatteryPercentage(){
  float batteryPercentage = ((getBatteryVoltage()-2.9) * 100)/1.3;
  if(batteryPercentage > 100){
    return 100;
  }
  else if(batteryPercentage <0){
    return 0;
  }
  return batteryPercentage;
}

float getBatteryVoltage(){
  return ((float)analogRead(PIN_BATTERY) * 6.6)/1023;
}

int getLight(){
  digitalWrite(PIN_LIGHT_ENABLE, HIGH);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(200));
  int a = analogRead(PIN_LIGHT_READ);
  digitalWrite(PIN_LIGHT_ENABLE, LOW);
  return a;
}

bool updateWeight(){
  weightSensor.power_up();
  if(weightSensor.wait_ready_timeout(1000)){
    weightSensor.set_offset(DEFAULT_WEIGHT_OFFSET);
    weightSensor.set_scale(DEFAULT_WEIGHT_SCALE);
    weight = weightSensor.get_units(10);
    weightSensor.power_down();
    return true;
  }
  else{
    weightSensor.power_down();
    return false;
  }
}

float getWeight(){  //Retourne poid en kg
  return weight;
}

UBeeZ_Battery_weight_light.h

C Header File

CODE
#ifndef UBEEZ_BATTERY_WEIGHT_LIGHT
#define UBEEZ_BATTERY_WEIGHT_LIGHT

#define PIN_BATTERY A1
#define PIN_LIGHT_ENABLE 4
#define PIN_LIGHT_READ A6

#define PIN_WEIGHT_DOUT 6
#define PIN_WEIGHT_PSCK 7

#define DEFAULT_WEIGHT_OFFSET 107975
#define DEFAULT_WEIGHT_SCALE 29763

void setupBatteryLight();
void setupWeight();
float getBatteryPercentage();
float getBatteryVoltage();
float getWeight();
bool updateWeight();
int getLight();

#endif

UBeeZ_Bluetooth.cpp

C/C++

CODE
#include "UBeeZ_Bluetooth.h"
#include <mbed.h>
#include "UBeeZ_LED.h"
#include "UBeeZ_Battery_weight_light.h"
#include "UBeeZ_Temperature_humidity_sensor.h"
#include "UBeeZ_Prefs.h"

int taskBLEPoll = 0;
int taskBLEDisableUnused = 0;
int taskBLEDisableConnected = 0;
int taskBLEUpdateValues = 0;
bool stopBLEPollThread = false;
bool stopBLEUpdateValuesThread = false;
bool BLEEnabling = false;

events::EventQueue *BLEEventQueue;
rtos::Thread BLEEventThread;

BLEService BLEServiceUBeeZ("181A"); // create service
BLEService BLEServiceUBeeZConfig("180A"); // create service

//Bluetooth temperature
BLEShortCharacteristic BLECharacteristicTemperature[] = {BLEShortCharacteristic("00002A6E-0000-1000-8000-00805F9B34FB", BLERead | BLENotify),
BLEShortCharacteristic("00002A6E-0000-1000-8000-00805F9B34FB", BLERead | BLENotify), BLEShortCharacteristic("00002A6E-0000-1000-8000-00805F9B34FB", BLERead | BLENotify),
BLEShortCharacteristic("00002A6E-0000-1000-8000-00805F9B34FB", BLERead | BLENotify)};
uint8_t descriptorTemperature[4][7] = {{0x0E, (uint8_t)(int8_t)-2, 0x2F, 0x27, 0x01, 0x01, 0x00}, {0x0E, (uint8_t)(int8_t)-2, 0x2F, 0x27, 0x01, 0x02, 0x00},
{0x0E, (uint8_t)(int8_t)-2, 0x2F, 0x27, 0x01, 0x03, 0x00}, {0x0E, (uint8_t)(int8_t)-2, 0x2F, 0x27, 0x01, 0x04, 0x00}};
BLEDescriptor BLEDescriptorTemperature[4] = {BLEDescriptor("2904", descriptorTemperature[0], 7), BLEDescriptor("2904", descriptorTemperature[1], 7),
BLEDescriptor("2904", descriptorTemperature[2], 7), BLEDescriptor("2904", descriptorTemperature[3], 7)};

//Bluetooth humidite
BLEUnsignedShortCharacteristic BLECharacteristicHumidite[] = {BLEUnsignedShortCharacteristic("00002A6F-0000-1000-8000-00805F9B34FB", BLERead | BLENotify),
BLEUnsignedShortCharacteristic("00002A6F-0000-1000-8000-00805F9B34FB", BLERead | BLENotify)};
uint8_t descriptorHumidite[2][7] = {{0x06, (uint8_t)(int8_t)-2, 0xAD, 0x27, 0x01, 0x0B, 0x01}, {0x06, (uint8_t)(int8_t)-2, 0xAD, 0x27, 0x01, 0x0C, 0x01}};
BLEDescriptor BLEDescriptorHumidite[2] = {BLEDescriptor("2904", descriptorHumidite[0], 7), BLEDescriptor("2904", descriptorHumidite[1], 7)};

//Bluetooth poid
BLEUnsignedShortCharacteristic BLECharacteristicPoid("00002A98-0000-1000-8000-00805F9B34FB", BLERead | BLENotify);
uint8_t descriptorPoid[7] = {0x06, (uint8_t)(int8_t)-2, 0x02, 0x27, 0x01, 0x00, 0x00};
BLEDescriptor BLEDescriptorPoid("2904", descriptorPoid, 7);

//Bluetooth batterie
BLEByteCharacteristic BLECharacteristicBatterie("00002A19-0000-1000-8000-00805F9B34FB", BLERead | BLENotify);
uint8_t descriptorBatterie[7] = {0x04, 0x00, 0xAD, 0x27, 0x01, 0x00, 0x00};
BLEDescriptor BLEDescriptorBatterie("2904", descriptorBatterie, 7);

//Bluetooth luminosite
BLECharacteristic BLECharacteristicLuminosite("00002AFB-0000-1000-8000-00805F9B34FB" , BLERead | BLENotify, 3);
uint8_t descriptorLuminosite[7] = {0x7, (uint8_t)(int8_t)-2, 0x31, 0x27, 0x01, 0x0C, 0x01};
BLEDescriptor BLEDescriptorLuminosite("2904", descriptorLuminosite, 7);

//Bluetooth syncDelay
BLEUnsignedShortCharacteristic BLECharacteristicSyncDelay("00002A21-0000-1000-8000-00805F9B34FB" , BLERead | BLEWrite);
uint8_t descriptorSyncDelay[7] = {0x6, 0x00, 0x03, 0x27, 0x1, 0x00, 0x00};
BLEDescriptor BLEDescriptorSyncDelay("2904", descriptorSyncDelay, 7);

//Bluetooth localisation
BLECharacteristic BLECharacteristicLocalisation("00002A67-0000-1000-8000-00805F9B34FB", BLERead | BLEWrite, 12);
uint8_t descriptorLocalisation[4][7] = {{0x1B, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00}, {0x10, (uint8_t)(int8_t)-7, 0x63, 0x27, 0x01, 0x00, 0x00},
{0x10, (uint8_t)(int8_t)-7, 0x63, 0x27, 0x01, 0x00, 0x00}, {0x06, (uint8_t)(int8_t)-2, 0x63, 0x27, 0x01, 0x00, 0x00}};
BLEDescriptor BLEDescriptorLocalisation[4] = {BLEDescriptor("2904", descriptorLocalisation[0], 7), BLEDescriptor("2904", descriptorLocalisation[1], 7),
BLEDescriptor("2904", descriptorLocalisation[2], 7), BLEDescriptor("2904", descriptorLocalisation[3], 7)};

void blePeripheralConnectHandler(BLEDevice central) {
  if(BLE_DEBUG){
    SerialUSB.println("blePeripheralConnectHandler");
  }
  led_stop_B();
  digitalWrite(LEDB, LOW);
  if(taskBLEDisableUnused !=0){
    BLEEventQueue->cancel(taskBLEDisableUnused);
    taskBLEDisableUnused = 0;
  }
  if(taskBLEDisableConnected == 0 && !BLE_ALWAYS_ACTIVE){
    taskBLEDisableConnected = BLEEventQueue->call_in(BLE_CONNECTED_TIMEOUT, (void(*)(void))disableBLE);
  }
  BLE.stopAdvertise();
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  if(BLE_DEBUG){
    SerialUSB.println("blePeripheralDisconnectHandler");
  }
  digitalWrite(LEDB, HIGH);
  led_blink_B(1000);
  if(taskBLEDisableConnected !=0){
    BLEEventQueue->cancel(taskBLEDisableConnected);
    taskBLEDisableConnected = 0;
  }
  if(taskBLEDisableUnused == 0 && !BLE_ALWAYS_ACTIVE){
    taskBLEDisableUnused = BLEEventQueue->call_in(BLE_UNUSED_TIMEOUT, (void(*)(void))disableBLE);
  }
  BLE.advertise();
}

void setupBLE(){
  if(BLE_DEBUG){
    SerialUSB.println("setupBLE");
  }
  pinMode(BLE_BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BLE_BUTTON_PIN), startBLE, FALLING);
  
  for(int i=0;i<4;i++){
    BLECharacteristicTemperature[i].addDescriptor(BLEDescriptorTemperature[i]);
    BLEServiceUBeeZ.addCharacteristic(BLECharacteristicTemperature[i]);
  }
  for(int i=0;i<2;i++){
    BLECharacteristicHumidite[i].addDescriptor(BLEDescriptorHumidite[i]);
    BLEServiceUBeeZ.addCharacteristic(BLECharacteristicHumidite[i]);
  }
  BLECharacteristicPoid.addDescriptor(BLEDescriptorPoid);
  BLEServiceUBeeZ.addCharacteristic(BLECharacteristicPoid);

  BLECharacteristicBatterie.addDescriptor(BLEDescriptorBatterie);
  BLEServiceUBeeZ.addCharacteristic(BLECharacteristicBatterie);

  BLECharacteristicLuminosite.addDescriptor(BLEDescriptorLuminosite);
  BLEServiceUBeeZ.addCharacteristic(BLECharacteristicLuminosite);

  BLECharacteristicSyncDelay.addDescriptor(BLEDescriptorSyncDelay);
  BLECharacteristicSyncDelay.setEventHandler(BLEWritten, syncDelayCharacteristicWritten);
  BLEServiceUBeeZConfig.addCharacteristic(BLECharacteristicSyncDelay);

  for(int i=0;i<4;i++){
    BLECharacteristicLocalisation.addDescriptor(BLEDescriptorLocalisation[i]);
  }
  BLECharacteristicLocalisation.setEventHandler(BLEWritten, localisationCharacteristicWritten);
  BLEServiceUBeeZConfig.addCharacteristic(BLECharacteristicLocalisation);

  BLEEventQueue = new events::EventQueue(5 * EVENTS_EVENT_SIZE);
  BLEEventThread.start(callback(BLEEventQueue, &events::EventQueue::dispatch_forever));
}

void enableBLE(){
  if(taskBLEPoll!=0){
    BLE.disconnect();
    return;
  }
  if(BLE_DEBUG){
    SerialUSB.println("enableBLE");
  }
  taskBLEPoll = BLEEventQueue->call_in(BLE_POLL_INTERVAL, (void(*)(void))pollBLE);
  taskBLEUpdateValues = BLEEventQueue->call_in(BLE_UPDATE_VALUES_INTERVAL, (void(*)(void))updateBLEValues);
  if(!BLE_ALWAYS_ACTIVE){
    taskBLEDisableUnused = BLEEventQueue->call_in(BLE_UNUSED_TIMEOUT, (void(*)(void))disableBLE);
  }
  
  if(!BLE.begin()){
    int delay_ms = 500;
    led_blink_B(delay_ms);
    led_blink_R(delay_ms);
    disableBLE();
    return;
  }
  else{
    led_blink_B(1000);
    led_stop_R();
  }

  BLE.setLocalName("UBeeZ");
  BLE.setDeviceName("UBeeZ");
  BLE.setAppearance(0x0552);

  BLE.setAdvertisedService(BLEServiceUBeeZ);
  BLE.addService(BLEServiceUBeeZ);
  BLE.addService(BLEServiceUBeeZConfig);

  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
  BLE.advertise();
  if(BLE_DEBUG){
    SerialUSB.println("enabled BLE");
  }
}

void disableBLE(){
  if(BLE_DEBUG){
    SerialUSB.println("disableBLE");
  }
  led_stop_B();
  led_stop_R();
  digitalWrite(LEDB, HIGH);
  digitalWrite(LEDR, HIGH);
  if(taskBLEPoll!=0){
    stopBLEPollThread = true;
  }
  if(taskBLEUpdateValues!=0){
    stopBLEUpdateValuesThread = true;
  }
  
  if(taskBLEDisableUnused !=0){
    BLEEventQueue->cancel(taskBLEDisableUnused);
    taskBLEDisableUnused = 0;
  }
  if(taskBLEDisableConnected !=0){
    BLEEventQueue->cancel(taskBLEDisableConnected);
    taskBLEDisableConnected = 0;
  }
  BLE.end();
}

void pollBLE(){
  /*if(BLE_DEBUG){
    SerialUSB.println("pollBLE");
  }*/
  if(stopBLEPollThread){
    taskBLEPoll = 0;
    stopBLEPollThread = false;
  }
  else{
    BLE.poll(200);
    taskBLEPoll = BLEEventQueue->call_in(BLE_POLL_INTERVAL, (void(*)(void))pollBLE);
  }
}

void startBLE(){
  BLEEventQueue->call((void(*)(void))enableBLE);
}

void updateBLEValues(){
  if(BLE_DEBUG){
    SerialUSB.println("updateBLEValues");
  }
  if(stopBLEUpdateValuesThread){
    taskBLEUpdateValues = 0;
    stopBLEUpdateValuesThread = false;
    return;
  }
  updateTemperatureHumidity();
  for(int i=0;i<4;i++){
    float temperature = getTemperatureSensor(i);
    if(isnan(temperature)){
      BLECharacteristicTemperature[i].writeValue(82.3*100);//Temperature reading error
    }
    else{
      BLECharacteristicTemperature[i].writeValue(temperature*100);
    }
  }

  for(int i=0;i<2;i++){
    float humidite = getHumiditeSensor(i);
    if(isnan(humidite)){
      BLECharacteristicHumidite[i].writeValue(105*100);//Humidite reading error
    }
    else{
      BLECharacteristicHumidite[i].writeValue(humidite*100);
    }
  }

  if(updateWeight()){
    BLECharacteristicPoid.writeValue(getWeight()*200);
  }
  else{
    BLECharacteristicPoid.writeValue(102.3*200);
  }
  
  BLECharacteristicBatterie.writeValue(getBatteryPercentage());

  float light = getLight();
  
  if(light<=0){//Erreur dconnect
    light=1023;
  }
  else if(light>1022){
    light=1022;
  }
  light = 11.019*exp(0.0069*light);
  
  BLECharacteristicLuminosite.writeValue((uint32_t)(light*100));

  BLECharacteristicSyncDelay.writeValue(getPrefs()->sigfoxSyncDelay);

  uint8_t localisation[12];
  for(int i=0;i<12;i++){
    localisation[i] = 0;
  }
  localisation[0] = 0x14;
  localisation[1] = 0x10;
  int latitude = 123.4567899*10000000;//getPrefs()->gpsLatitude;
  int longitude = -123.4567899*10000000;//getPrefs()->gpsLongitude;
  unsigned short heading = 69.69*100;//getPrefs()->gpsHeading;
  localisation[2] = latitude;
  localisation[3] = latitude >> 8;
  localisation[4] = latitude >> 16;
  localisation[5] = latitude >> 24;
  localisation[6] = longitude;
  localisation[7] = longitude >> 8;
  localisation[8] = longitude >> 16;
  localisation[9] = longitude >> 24;
  localisation[10] = heading;
  localisation[11] = heading >> 8;
  

  BLECharacteristicLocalisation.writeValue(localisation, 12, true);

  taskBLEUpdateValues = BLEEventQueue->call_in(BLE_UPDATE_VALUES_INTERVAL, (void(*)(void))updateBLEValues);
}

void syncDelayCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  unsigned short newDelay = (characteristic.value()[1] << 8 | characteristic.value()[0]);
  if(BLE_DEBUG){
    SerialUSB.print("syncDelayCharacteristicWritten new value : ");
    SerialUSB.println(newDelay);
  }
  
  if(newDelay >= 600 || DEBUG_PREFS){
    getPrefs()->sigfoxSyncDelay = newDelay;
    writePrefs();
  }
  else{
    BLECharacteristicSyncDelay.writeValue(getPrefs()->sigfoxSyncDelay);
  }
}

void localisationCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  double newLatitude = ((double)(characteristic.value()[2] | characteristic.value()[3] << 8 | characteristic.value()[4] << 16 | characteristic.value()[5] << 24)) / 10000000;
  double newLongitude = ((double)(characteristic.value()[6] | characteristic.value()[7] << 8 | characteristic.value()[8] << 16 | characteristic.value()[9] << 24)) / 10000000;
  float newHeading = ((float)(characteristic.value()[10] | characteristic.value()[11] << 8)) / 100;
  
  if(BLE_DEBUG){
    SerialUSB.println("localisationCharacteristicWritten new value : ");
    SerialUSB.print("Latitude : ");
    SerialUSB.println(newLatitude, 7);
    SerialUSB.print("Longitude : ");
    SerialUSB.println(newLongitude, 7);
    SerialUSB.print("Heading : ");
    SerialUSB.println(newHeading, 2);
  }  
}

UBeeZ_Bluetooth.h

C Header File

CODE
#ifndef UBEEZ_BLUETOOTH
#define UBEEZ_BLUETOOTH

#include <ArduinoBLE.h>

#define BLE_UNUSED_TIMEOUT std::chrono::seconds(30)
#define BLE_CONNECTED_TIMEOUT std::chrono::seconds(60*15)
#define BLE_POLL_INTERVAL std::chrono::milliseconds(10)
#define BLE_UPDATE_VALUES_INTERVAL std::chrono::seconds(1)
#define BLE_BUTTON_PIN 2  //Digital pin 2 (D2)
#define BLE_ALWAYS_ACTIVE false
#define BLE_DEBUG false

void blePeripheralConnectHandler(BLEDevice central);
void blePeripheralDisconnectHandler(BLEDevice central);
void setupBLE();
void enableBLE();
void disableBLE();
void pollBLE();
void startBLE();
void updateBLEValues();
void syncDelayCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic);
void localisationCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic);

#endif

UBeeZ_GPS.cpp

C/C++

CODE
#include "UBeeZ_GPS.h"
#include "UBeeZ_LED.h"
#include "UBeeZ_Prefs.h"
#include <Serial.h>
#include "mbed.h"
#include "rtos.h"
#include <TinyGPS++.h>
#include <chrono>

using namespace rtos;

TinyGPSPlus gps;

std::chrono::time_point<std::chrono::system_clock> end_time;

bool DISABLE_GPS = false;
bool gpsEnabled = false;

void setupGPS(){
  if(GPS_DEBUG){
    SerialUSB.println("setupGPS");
  }
  
  Serial2.begin(9600);
  
  pinMode(PIN_TPL_DELAY, OUTPUT);
  pinMode(PIN_TPL_DONE, OUTPUT);

  digitalWrite(PIN_TPL_DELAY, LOW);
  digitalWrite(PIN_TPL_DONE, LOW);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_TPL_DELAY, HIGH);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_TPL_DELAY, LOW);

  led_blink_G(100); 
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(200));
  std::chrono::time_point<std::chrono::system_clock> time_out= std::chrono::system_clock::now() + std::chrono::seconds(5);
  while(!Serial2.available() && std::chrono::system_clock::now() < time_out){
    rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  }

  gpsEnabled = std::chrono::system_clock::now() < time_out;
  
  led_stop_G();
  digitalWrite(LEDG, HIGH);
  digitalWrite(PIN_TPL_DONE, HIGH);
  rtos::ThisThread::sleep_for(std::chrono::seconds(1));
  digitalWrite(PIN_TPL_DONE, LOW);
  if(GPS_DEBUG){
    if(gpsEnabled){
      Serial.println("GPS setup completed !");
    }
    else{
      Serial.println("GPS setup failed !");
    }
  }
}

bool get_GPS_location(){
  if(GPS_DEBUG){
    SerialUSB.println("get_GPS_location start");
  }
  if(!isGPSEnabled()){
    if(GPS_DEBUG){
      SerialUSB.println("get_GPS_location failed (gps not enabled)");
    }
    return false;
  }
  digitalWrite(PIN_TPL_DELAY, HIGH);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_TPL_DELAY, LOW);
  
  end_time = std::chrono::system_clock::now() + GPS_UPTIME;

  bool stop_GPS = false;
  bool got_GPS_signal = false;
  while(!stop_GPS){
    while(Serial2.available()){
      gps.encode(Serial2.read());
    }
    if(GPS_DEBUG){
      if(gps.satellitesAll.isUpdated()){
        SerialUSB.print("Sat all : ");
        SerialUSB.println(gps.satellitesAll.value());
      }
    }
    
    if(gps.location.isUpdated()){
      double newLat = gps.location.lat();
      double newLng = gps.location.lng();
      if(GPS_DEBUG){
        SerialUSB.println("GOT LOCATION !");
        SerialUSB.print("Lat : ");
        SerialUSB.println(newLat, 6);
        SerialUSB.print("Long : ");
        SerialUSB.println(newLng, 6);
        SerialUSB.print("Sat : ");
        SerialUSB.println(gps.satellites.value());
      }
      getPrefs()->gpsLatitude = newLat;
      getPrefs()->gpsLongitude = newLng;
      writePrefs();
      stop_GPS = true;
      got_GPS_signal = true;
    }
    else if((gps.satellitesAll.value() <= 0 && std::chrono::system_clock::now() >= end_time)
              || (gps.satellitesAll.value() > 0 && std::chrono::system_clock::now() >= end_time+GPS_ADDITIONNAL_UPTIME)){
      if(GPS_DEBUG){
        SerialUSB.println("GPS timeout (no fix) !");
      }
      stop_GPS = true;
    }
    rtos::ThisThread::sleep_for(GPS_TICK);
  }

  
  if(GPS_DEBUG){
    SerialUSB.println("stoping GPS");
  }
  digitalWrite(PIN_TPL_DONE, HIGH);
  rtos::ThisThread::sleep_for(std::chrono::milliseconds(500));
  digitalWrite(PIN_TPL_DONE, LOW);
  if(GPS_DEBUG){
    SerialUSB.println("get_GPS_location done");
  }

  return got_GPS_signal;
}

bool isGPSEnabled(){
  return gpsEnabled;
}

UBeeZ_GPS.h

C Header File

CODE
#ifndef UBEEZ_GPS
#define UBEEZ_GPS

#define PIN_TPL_DELAY 3
#define PIN_TPL_DONE 13

#define GPS_DEBUG true
#define GPS_UPTIME std::chrono::minutes(1)
#define GPS_ADDITIONNAL_UPTIME std::chrono::minutes(2)
#define GPS_TICK std::chrono::seconds(1)

void setupGPS();
bool get_GPS_location();

bool isGPSEnabled();

#endif

UBeeZ_LED.cpp

C/C++

CODE
#include "UBeeZ_LED.h"

using namespace rtos;

events::EventQueue *ledEventQueue;
Thread ledEventThread;

bool stop_led_R_flag = false;
bool stop_led_G_flag = false;
bool stop_led_B_flag = false;

int led_R_task = 0;
int led_G_task = 0;
int led_B_task = 0;

byte led_R_state = HIGH;
byte led_G_state = HIGH;
byte led_B_state = HIGH;

std::chrono::milliseconds led_R_delay_ms(1000);
std::chrono::milliseconds led_G_delay_ms(1000);
std::chrono::milliseconds led_B_delay_ms(1000);

void setupLedRGB(){
  pinMode(LEDB, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LED_PWR, OUTPUT);
  
  digitalWrite(LEDR, HIGH);
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);
  digitalWrite(LED_BUILTIN, LOW);
  digitalWrite(LED_PWR, LOW);
  ledEventQueue = new events::EventQueue(3 * EVENTS_EVENT_SIZE);
  ledEventThread.start(callback(ledEventQueue, &events::EventQueue::dispatch_forever));
}

void led_blink_R(int delay_ms){
  led_R_delay_ms = std::chrono::milliseconds(delay_ms);
  stop_led_R_flag = false;
  if(led_R_task == 0){
    led_R_task = ledEventQueue->call_in(led_R_delay_ms, (void(*)(void))handler_led_blink_R);
  }
}

void led_blink_G(int delay_ms){
  led_G_delay_ms = std::chrono::milliseconds(delay_ms);
  stop_led_G_flag = false;
  if(led_G_task == 0){
    led_G_task = ledEventQueue->call_in(led_G_delay_ms, (void(*)(void))handler_led_blink_G);
  }
}

void led_blink_B(int delay_ms){
  led_B_delay_ms = std::chrono::milliseconds(delay_ms);
  stop_led_B_flag = false;
  if(led_B_task == 0){
    led_B_task = ledEventQueue->call_in(led_B_delay_ms, (void(*)(void))handler_led_blink_B);
  }
}

void led_stop_R(){
  stop_led_R_flag = true;
}

void led_stop_G(){
  stop_led_G_flag = true;
}

void led_stop_B(){
  stop_led_B_flag = true;
}

void handler_led_blink_R(){
  if(!stop_led_R_flag){
    led_R_task = ledEventQueue->call_in(led_R_delay_ms, (void(*)(void))handler_led_blink_R);
    led_R_state = !led_R_state;
    digitalWrite(LEDR, led_R_state);
  }
  else{
    stop_led_R_flag = false;
    led_R_task = 0;
  }
}

void handler_led_blink_G(){
  if(!stop_led_G_flag){
    led_G_task = ledEventQueue->call_in(led_G_delay_ms, (void(*)(void))handler_led_blink_G);
    led_G_state = !led_G_state;
    digitalWrite(LEDG, led_G_state);
  }
  else{
    stop_led_G_flag = false;
    led_G_task = 0;
  }
}

void handler_led_blink_B(){
  if(!stop_led_B_flag){
    led_B_task = ledEventQueue->call_in(led_B_delay_ms, (void(*)(void))handler_led_blink_B);
    led_B_state = !led_B_state;
    digitalWrite(LEDB, led_B_state);
  }
  else{
    stop_led_B_flag = false;
    led_B_task = 0;
  }
}

UBeeZ_LED.h

C Header File

CODE
#ifndef UBEEZ_LED
#define UBEEZ_LED

#include <mbed.h>
#include <rtos.h>

void setupLedRGB();

void led_blink_R(int delay_ms);
void led_blink_G(int delay_ms);
void led_blink_B(int delay_ms);

void led_stop_R();
void led_stop_G();
void led_stop_B();

void handler_led_blink_R();
void handler_led_blink_G();
void handler_led_blink_B();

#endif
icon UBeeZ-main.zip 647KB Download(0)

The article was first published in hackster, January 10, 2022

cr: https://www.hackster.io/clementchamayou/how-to-monitor-a-beehive-with-arduino-nano-33ble-bluetooth-eabc0d

author: Clément Chamayou, Jade EVRARD, Margaux Launois, Jeremy Royer, Nicolas Stein

License
All Rights
Reserved
licensBg
0