Troubles combining thinger with freertos

Hi There

I am not super familiar with thinger.io and I am having some troubles getting it to work the way I want. I am prototyping with an Arduino MKR NB cellular device which I have managed to get communicating to thinger in a basic sense.

My goal is to create a program with multiple tasks running at different intervals (GPS, main loop, comms etc). I need to do this so that I can optimise the battery usage of the device. In particular I eventually only want the cellular chip to be sending and receiving data ever 3 mins (1 min for now).

For this reason I am using freertos for my task management but running into trouble getting it to work with thinger. When I put the thing.handle function into a thread it just doesn’t work. For now I solved the problem by using the thing.write_bucket functionality, which does work inside a freertos task, to push data to the cloud. Problem is now I want to be able to retrieve some geoJson data from cloud and the only way to do that seems to be to use the thing.handle function.

My questions are:

  • is there a function that I could use instead of .handle to directly retrieve json data from the platform?
  • what is best practice for retrieving json data from the cloud
  • is there maybe actually a better way to control how often thinger pings the backend and therefore save battery life?
  • is thinger and freertos actually compatible or should I use a different methodology?

Thanks in advance for your help!

My code is below for reference.

#include <Arduino.h>
#include <Wire.h> 
#include <ChainableLED.h>
#define THINGER_SERIAL_DEBUG
#define _DISABLE_TLS_
#include <ThingerMKRNB.h>
#include <MKRNB.h>
#include <arduino_secrets.h>
#include <location.h>
#include <heading.h>
#include <cowtrol.h>
#include <FreeRTOS_SAMD21.h>

TaskHandle_t taskGPSHandle;
TaskHandle_t mainHandle;
TaskHandle_t locationHandle;
TaskHandle_t checkForZoneHandle;

SemaphoreHandle_t mutex;

#define GPS_DELAY_MS 400 // (ms) - 2Hz
#define MAIN_DELAY_MS 200
#define LOCATION_DELAY_MS 60000 // 60 seconds
#define NUM_LEDS  1

// class initilisation
locationServices locationService;
headingServices headingService;
cowtrolServices cowtrolService;
ThingerMKRNB thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ChainableLED leds(5, 6, NUM_LEDS); 

// // zone polygon vector - hardcoded for now - TNW
// std::vector<latLonPoint> zonePolygon = {
//     {52.36684584896231, 4.892382884038189},
//     {52.36653316197476, 4.892287194486286},
//     {52.36645977946135, 4.892898433659862},
//     {52.36680868093923, 4.893030724121128},
//     {52.36684584896231, 4.892382884038189}
// };

// zone polygon vector - hardcoded for now - Leigh and Nisal
std::vector<latLonPoint> zonePolygon = {
    {52.35334402186569, 4.846027536843199},
    {52.35335365905127, 4.847232301428632},
    {52.35448202568118, 4.847126262277273},
    {52.35447509518661, 4.846395306438856},
    {52.35392988366807, 4.845993872750524},
    {52.35334402186569, 4.846027536843199}
};

// Amsterdam location
latLonPoint datum {52.3676, 4.9041};

// global variablesx
int relativeHeading;
latLonPoint location {datum.latitude, datum.longitude}; //set starting location as datum

void taskProcessGPS(void* parameter) {
  
  TickType_t lastWakeTime = xTaskGetTickCount();

  while (true) {

    // xSemaphoreTake(mutex, portMAX_DELAY);

    location = locationService.readGPS();
    locationService.checkInZone(location, zonePolygon, datum);

    // xSemaphoreGive(mutex);
    vTaskDelay(pdMS_TO_TICKS(300));
  }
}

void mainLoop(void* parameter) {

  TickType_t lastWakeTime = xTaskGetTickCount();

  while (1) {

    // xSemaphoreTake(mutex, portMAX_DELAY);

    if (!locationService.insideZone) {
      
      Serial.println("Cowgorithm Enabled");
      leds.setColorRGB(0, 255, 0, 0);
      relativeHeading = headingService.getRelativeHeading(headingService.getHeading(), locationService.getCompassHeading());
      // cowtrolService.runCowtrol(relativeHeading);
      
    }
    else {
      cowtrolService.stopPWM();
      Serial.println("Cowgorithm Disabled");
      leds.setColorRGB(0, 0, 255, 0);
    }

    // xSemaphoreGive(mutex);
    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(MAIN_DELAY_MS));
  }
}

void sendLocation(void* parameter) {
  TickType_t lastWakeTime = xTaskGetTickCount();
  while (1) {

    // xSemaphoreTake(mutex, portMAX_DELAY);
    Serial.println("Sending Update");
    thing.write_bucket("deviceUpdate", "deviceUpdate");

    // xSemaphoreGive(mutex);
    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(LOCATION_DELAY_MS));
  }
}

void checkForZone(void* parameter) {
  TickType_t lastWakeTime = xTaskGetTickCount();
  while (1) {
    thing.handle();
    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(LOCATION_DELAY_MS));
  }
}

void setup(void)
{
  // mutex = xSemaphoreCreateMutex();
  // leds.setColorRGB(0, 0, 0, 0);
  Serial.begin(9600);

  // while (!Serial) delay(10);  // wait for serial port to open!

  Serial.println("Started");

  leds.setColorRGB(0, 255, 0, 0);
  headingService.initialise();
  // headingService.calibrate();
  leds.setColorRGB(0, 0, 255, 0);
  delay(1000);
  leds.setColorRGB(0, 0, 0, 0);

  // optional set pin number
  thing.set_pin(SECRET_PINNUMBER);
  // set APN
  thing.set_apn(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD);

//   thing["sendZone"] >> [](pson& in){
//     const char* data = in;
//     Serial.print("Received GeoJSON data: ");
//     Serial.println(data);
// };
      
  thing["deviceUpdate"] >> [](pson& out){
      out["lat"] = location.latitude;
      out["lon"] = location.longitude;
      out["heading"] = relativeHeading;
      // out['shockCount'] = cowtrolService.shockCount;
  };
  while (!thing.is_connected()){
    thing.handle();
  }

  Serial.println("NB Initialized");

  // setup services
  locationService.setupGPS();
  cowtrolService.setupPiezo();

  int count = 0;
  xTaskCreate(taskProcessGPS, "Process GPS", configMINIMAL_STACK_SIZE, NULL, 1, &taskGPSHandle);
  xTaskCreate(sendLocation, "Send Location", configMINIMAL_STACK_SIZE, NULL, 3, &locationHandle);
  // xTaskCreate(checkForZone, "Check For Zone", configMINIMAL_STACK_SIZE, NULL, 2, &checkForZoneHandle);
  xTaskCreate(mainLoop, "Main Loop", configMINIMAL_STACK_SIZE, NULL, 0, &mainHandle);
  vTaskStartScheduler();

}

void loop(void)
{
}

Hi @chris_cloverag,

I think that the problem is not to run the thing.handle() inside a task, but adding a 60 delay after the call:

vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(LOCATION_DELAY_MS));

The thing.handle should be called iteratively with minimum delay, as it manages several things internally, like network connection, keep alives, streaming resources, etc. Calling it just once and then sleeping, can cause problems.

So, I recommend you to remove this delay after the thing.handle.

Anyway, this will keep the modem alive during the whole period (even with the delay). I think that the sketch can be oriented in a different way, and probably does not require using RTOS for this task.

For a better battery duration, I would just go to deep sleep when the device is not updating the location:

LowPower.deepSleep(60 * 1000 * 5); // wait 5 minutes

And in each process start, just initialize GPS, connection, read geofences from a device property, check geofences, write to a bucket, and then go to deep sleep again.

Hi @alvarolb, thank you for the reply!

Thing is, I need to be updating the GPS position at 2hz continuously but only want to have the comms run every three minutes or so. Do you know of a way to have the device running while only talking to the platform every x minutes or is it only possible to have the connection, ad therefore the comms chip, running continuously?

Thanks!

Hi @chris_cloverag

I think is important to understand how thinger’s library works, note that all the communications have place in thing.handle();, when you call the write bucket instruction, data transmission is executed when thing.handle(); is called.

For your project, I would check the device consumption as it could take a current spike when connects (sim900 has it and could reach 2A according datasheet), and i think it worth a try to test in order to make sure you are applying the appropriate strategy turning off the chips instead letting them on just running “I’m alive” signals.

Hope this helps

Thanks,

I assume that checking in and sending data less often will improve battery life though? Is there a way to change/adjust the rate that the device actually checks in and sends/receives data to the back end. I don’t necessarily need the device to update instantaneously but only every 1-3 minutes.

For sure sending less data, less often improves battery lasting, on the other hand I doubt the alive beacon signal would make a significant difference, instead focus in that direction I would put my effort on, for example, make smaller data packages and double check what I told at previous message about what drains more energy if keeping the connection alive or disconnecting and reconnecting again.

Btw I think that the alive beacon check frequency is stablished at the cloud, at the server is where is stablished if a device is online or not, so I doubt it is a changeable setting.

Hope this helps.