Mithun Das
Published © GPL3+

Protect Peatlands for people and planet with help of SensiML

Conserve peatlands by predicting early sign of drying, drought or fire using ML on the edge using SensiML, Ubidots and Helium [SOLAR POWER]

IntermediateFull instructions provided20 hours2,308

Things used in this project

Hardware components

QuickFeather Development Kit
QuickLogic Corp. QuickFeather Development Kit
×1
Carbon Monoxide Sensor (MQ7)
DFRobot Carbon Monoxide Sensor (MQ7)
×1
Seeed Studio Seed Soil Moisture
×1
Adafruit ADS1015
×2
Gravity: Analog LM35 Temperature Sensor For Arduino
DFRobot Gravity: Analog LM35 Temperature Sensor For Arduino
×1
Heltec Strick Lite Wireles
×1

Software apps and online services

Helium
Arduino IDE
Arduino IDE
QuickLogic Corp. QORC SDK
Ubidots
Ubidots
SensiML Analytics Toolkit
SensiML Analytics Toolkit

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

peatgurad-top_Xfdrv8Q7Fk.stl

peatgurad-buttom_p93EJwjGw9.stl

Schematics

Circuit Diagram

Code

SensiMLHeltecArduino.ino

Arduino
#include <HardwareSerial.h>
#include <ESP32_LoRaWAN.h>
#include "Arduino.h"
#include <ArduinoJson.h>
#include <Wire.h>
#include <SparkFun_ADS1015_Arduino_Library.h> //Click here to get the library: http://librarymanager/All#SparkFun_ADS1015



#define VEXT_PIN 21                               // the board's VExt pin, used to power our sensors
#define BATT_VOLTAGE_PIN 13

StaticJsonDocument<192> doc;
#define PAYLOAD_SIZE 4 // Number of bytes sent plus 2

//const size_t capacity = JSON_OBJECT_SIZE(5) + 30;
//DynamicJsonDocument payload(capacity);
static uint8_t payload[PAYLOAD_SIZE];

uint32_t  license[4] = {0xC2090A24, 0x2B58148E, 0x8E2C7494, 0x4F4EAE8C};

/* OTAA para for helium*/
uint8_t DevEui[] = { get from helium console};
uint8_t AppEui[] = { get from helium console};
uint8_t AppKey[] = { get from helium console};


/* ABP para*/
uint8_t NwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda, 0x85 };
uint8_t AppSKey[] = { 0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef, 0x67 };
uint32_t DevAddr =  ( uint32_t )0x007e6ae1;



/*LoraWan Class, Class A and Class C are supported*/
DeviceClass_t  loraWanClass = CLASS_A;  //CLASS_A for battery powered device

/*the application data transmission duty cycle.  value in [ms].*/
uint32_t appTxDutyCycle = 30000;

/*OTAA or ABP*/
bool overTheAirActivation = true;

/*ADR enable*/
bool loraWanAdr = false;

/* Indicates if the node is sending confirmed or unconfirmed messages */
bool isTxConfirmed = false;

/* Application port */
uint8_t appPort = 2;


/*!
  Number of trials to transmit the frame, if the LoRaMAC layer did not
  receive an acknowledgment. The MAC performs a datarate adaptation,
  according to the LoRaWAN Specification V1.0.2, chapter 18.4, according
  to the following table:

  Transmission nb | Data Rate
  ----------------|-----------
  1 (first)       | DR
  2               | DR
  3               | max(DR-1,0)
  4               | max(DR-1,0)
  5               | max(DR-2,0)
  6               | max(DR-2,0)
  7               | max(DR-3,0)
  8               | max(DR-3,0)

  Note, that if NbTrials is set to 1 or 2, the MAC will not decrease
  the datarate, in case the LoRaMAC layer did not receive an acknowledgment
*/
uint8_t confirmedNbTrials = 8;

/*LoraWan debug level, select in arduino IDE tools.
  None : print basic info.
  Freq : print Tx and Rx freq, DR info.
  Freq && DIO : print Tx and Rx freq, DR, DIO0 interrupt and DIO1 interrupt info.
  Freq && DIO && PW: print Tx and Rx freq, DR, DIO0 interrupt, DIO1 interrupt and MCU deepsleep info.
*/
uint8_t debugLevel = LoRaWAN_DEBUG_LEVEL;

/*LoraWan region, select in arduino IDE tools*/
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;

RTC_DATA_ATTR static uint32_t bootcounter;
uint8_t decoded = 0;



int32_t myLatitude = 413094; // Initialize for testing before GPS finds a lock.
int32_t myLongitude = -722475; // Initialize for testing.



ADS1015 adcSensor;

float altitude(const int32_t press, const float seaLevel = 1013.25); ///< Forward function declaration with default value for sea level
float altitude(const int32_t press, const float seaLevel)
{
  /*!
    @brief     This converts a pressure measurement into a height in meters
    @details   The corrected sea-level pressure can be passed into the function if it is know, otherwise the standard
               atmospheric pressure of 1013.25hPa is used (see https://en.wikipedia.org/wiki/Atmospheric_pressure)
    @param[in] press    Pressure reading from BME680
    @param[in] seaLevel Sea-Level pressure in millibars
    @return    floating point altitude in meters.
  */
  static float Altitude;
  Altitude = 44330.0 * (1.0 - pow(((float)press / 100.0) / seaLevel, 0.1903)); // Convert into altitude in meters
  return (Altitude);
}



static char     buf[16];

int16_t gas = 0;
int16_t moisture = 0;
int16_t temperature = 0;
int16_t airpressure = 0;
int16_t bat = 0;
int classification = 0 ;
int retryCounter = 0;

static void prepareTxFrame( uint8_t port )
{


  Serial.printf("prepareTxFrame %d \n", bootcounter++);
  Serial.printf("GAS=%d SOIL=%d TEMP=%d BAT=%d CLAS=%d\n", gas, moisture, temperature, bat, classification);


  appDataSize = 18;//AppDataSize max value is 64

  appData[0] = byte(classification);
  appData[1] = classification >> 8;

  appData[2] = byte(myLatitude);
  appData[3] = myLatitude >> 8;
  appData[4] = myLatitude >> 16;

  appData[5] = byte(myLongitude);
  appData[6] = myLongitude >> 8;
  appData[7] = myLongitude >> 16;

  appData[8] = byte(temperature);
  appData[9] = temperature >> 8;

  appData[10] = byte(moisture);
  appData[11] = moisture >> 8;

  appData[12] = byte(airpressure);
  appData[13] = airpressure >> 8;

  appData[14] = byte(gas);
  appData[15] = gas >> 8;

  appData[16] = byte(bat);
  appData[17] = bat >> 8;


}

void low_power() {
  // go to sleep:
  // change pins to input
  // turn off power to sensors and radio
  digitalWrite(VEXT_PIN, HIGH);
  pinMode(VEXT_PIN, INPUT);
  pinMode(2, INPUT);
  pinMode(15, INPUT);
  pinMode(4, INPUT);
  pinMode(23, INPUT);

}

void setup() {

  pinMode(VEXT_PIN, OUTPUT);
  digitalWrite(VEXT_PIN, LOW);
  delay(200);

  Serial.begin(9600);
  Serial2.begin(460800, SERIAL_8N1, 2, 15);
  Wire.begin();

  SPI.begin(SCK, MISO, MOSI, SS);
  Mcu.init(SS, RST_LoRa, DIO0, DIO1, license);
  deviceState = DEVICE_STATE_INIT;
  delay(100);

  Serial.print(F("- Initializing ADS1015 sensor\n"));
  while (!adcSensor.begin() ) // Start ADS1015 using I2C protocol
  {
    Serial.print(F("-  Unable to find ADS1015. Trying again in 5 seconds.\n"));
    delay(5000);
  } // of loop until device is located

  adcSensor.setSampleRate(128);
  int16_t rate = adcSensor.getSampleRate();
  Serial.printf("Sample rate %d\n", rate);

  int16_t gain = adcSensor.getGain();
  Serial.printf("Gain %d\n", gain);

  delay(1000);

  gas = adcSensor.getSingleEnded(0); //MQ135
  moisture = adcSensor.getSingleEnded(1); //Soil Moisture
  temperature = adcSensor.getSingleEnded(2); //Temperature


  //float mv = ( channel_A2/1024.0)*5000;
  float mv = ( temperature / 4096.0) * 3300;
  float cel = mv / 10;
  temperature = (int16_t)( cel * 1.8 + 32);

  bat =  ReadVoltage(BATT_VOLTAGE_PIN) * 1000 ; //* 2.3857143;


    while (decoded != 1) {
      if (Serial2.available() > 0) {
        char bfr[501];
        memset(bfr, 0, 501);
        Serial2.readBytesUntil( '\r', bfr, 500);
        Serial.println(bfr);
        DeserializationError error = deserializeJson(doc, bfr);
  
        if (error) {
          Serial.printf("deserialization failed: %d", retryCounter);
          retryCounter = retryCounter + 1 ;
          if(retryCounter ==3){
            decoded =1;
          }
  
        } else {
          decoded = 1;
          classification = doc["Classification"];
          Serial.print("Classification:");
          Serial.println(classification);
        }
  
      }
    }

  

  Serial.println("Setup complete");

}


void loop() {


  switch ( deviceState )
  {
    case DEVICE_STATE_INIT:
      {
        LoRaWAN.init(loraWanClass, loraWanRegion);
        break;
      }
    case DEVICE_STATE_JOIN:
      {
        LoRaWAN.join();
        break;
      }
    case DEVICE_STATE_SEND:
      {
        prepareTxFrame( appPort );
        LoRaWAN.send(loraWanClass);


        deviceState = DEVICE_STATE_CYCLE;
        break;
      }
    case DEVICE_STATE_CYCLE:
      {
        // Schedule next packet transmission
        txDutyCycleTime = appTxDutyCycle + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND );
        LoRaWAN.cycle(txDutyCycleTime);
        deviceState = DEVICE_STATE_SLEEP;

        Serial.println("schedule next cycle");
        break;
      }
    case DEVICE_STATE_SLEEP:
      {
        low_power();
        LoRaWAN.sleep(loraWanClass, debugLevel);
        break;
      }
    default:
      {
        deviceState = DEVICE_STATE_INIT;
        break;
      }
  }


}

// a more accurate ADC read to within 1%
// https://github.com/HelTecAutomation/Heltec_ESP32/blob/master/examples/ESP32/ADC_Read_Voltage/ADC_Read_Accurate/ADC_Read_Accurate.ino


double ReadVoltage(byte pin) {
  double reading = analogRead(pin);
  return -0.000000000000016 * pow(reading, 4) + 0.000000000118171 * pow(reading, 3) - 0.000000301211691 * pow(reading, 2) + 0.001109019271794 * reading + 0.034143524634089;
}

sml_recognition_run.c

C/C++
//SensiML Includes
#include "kb.h"
#include "sml_output.h"
#include "sml_recognition_run.h"
//#include "dcl_commands.h"
#include "sensor_ssss.h"
//FILL_USE_TEST_DATA

#ifdef SML_USE_TEST_DATA
#include "testdata.h"
int td_index = 0;
#endif //SML_USE_TEST_DATA


int sml_recognition_run_batch(signed short *data_batch, int batch_sz, uint8_t num_sensors, uint32_t sensor_id)
{
	int ret;

	int batch_index = 0;
	signed short* data;
	for(batch_index=0; batch_index < batch_sz; batch_index++)
	{
	#ifdef SML_USE_TEST_DATA
	ret = kb_run_model((SENSOR_DATA_T*)&testdata[td_index++], TD_NUMCOLS, 0);
	if(td_index >= TD_NUMROWS)
	{
		td_index = 0;
	}
	if(ret >= 0)
	{
		kb_print_model_result(0, ret);
		sml_output_results(0, ret);
		kb_reset_model(0);
	}
	#else
	data = &data_batch[batch_index*num_sensors];
	ret = kb_run_model((SENSOR_DATA_T *)data, num_sensors, KB_MODEL_pipeline_1_rank_2_INDEX);
	if (ret >= 0){
		sml_output_results(KB_MODEL_pipeline_1_rank_2_INDEX, ret);
		kb_reset_model(0);
	};

	#endif //SML_USE_TEST_DATA
	}
	return ret;
}

int sml_recognition_run_single(signed short *data, uint32_t sensor_id)
{
	int ret;
	uint8_t num_sensors = 0;
	#ifdef SML_USE_TEST_DATA
	ret = kb_run_model((SENSOR_DATA_T*)&testdata[td_index++], TD_NUMCOLS, 0);
	if(td_index >= TD_NUMROWS)
	{
		td_index = 0;
	}
	if(ret >= 0)
	{
		kb_print_model_result(0, ret);
		sml_output_results(0, ret);
		kb_reset_model(0);
	}
	#else
			ret = kb_run_model((SENSOR_DATA_T *)data, num_sensors, KB_MODEL_pipeline_1_rank_2_INDEX);
			if (ret >= 0){
				sml_output_results(KB_MODEL_pipeline_1_rank_2_INDEX, ret);
				kb_reset_model(0);
			};
	#endif //SML_USE_TEST_DATA
	return ret;
}

sml_recognition_run.h

C/C++
#ifndef __SENSIML_RECOGNITION_RUN_H__
#define __SENSIML_RECOGNITION_RUN_H__

#ifdef __cplusplus
extern "C" {
#endif

int sml_recognition_run_batch(signed short *data_batch, int batch_sz, uint8_t num_sensors, uint32_t sensor_id);
int sml_recognition_run_single(signed short *data, uint32_t sensor_id);

#ifdef __cplusplus
}
#endif

#endif //__SENSIML_RECOGNITION_RUN_H__

sensor_ssss.cpp

C/C++
/** @file sensor_ssss_process.c */

/*==========================================================
 * Copyright 2020 QuickLogic Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *==========================================================*/

#include <stdbool.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "timers.h"

#include "datablk_mgr.h"
#include "process_ids.h"
#include "datablk_processor.h"
#include "sensor_ssss.h"
#include "micro_tick64.h"
#include "dbg_uart.h"
#include "ssi_comms.h"
#include "eoss3_hal_i2c.h"

// #include "mc3635_wire.h"
#include "sml_recognition_run.h"

#include "SparkFun_ADS1015_Arduino_Library.h"

// When enabled, GPIO (configured in pincfg_table.c) is toggled whenever a
// datablock is dispacthed for writing to the UART. Datablocks are dispatched
// every (SENSOR_SSSS_LATENCY) ms
#if (SENSOR_SSSS_RATE_DEBUG_GPIO == 1)
#include "eoss3_hal_gpio.h"
uint8_t sensor_rate_debug_gpio_val = 1;
#endif

/* BEGIN user include files
 * Add header files needed for accessing the sensor APIs
 */

/* User settable MACROs */

/* This section defines MACROs that may be user modified */

// MC3635  qorc_ssi_accel;
ADS1015 qorc_ssi_adc;

/* User modifiable sensor descriptor in JSON format */
/* BEGIN JSON descriptor for the sensor configuration */

const char json_string_sensor_config[] = \
"{"\
   "\"sample_rate\":100,"\
   "\"samples_per_packet\":6,"\
   "\"column_location\":{"\
	"  \"AirQualitySensor\":0,"\
	"  \"MoistureSensor\":1,"\
	"  \"TemperatureSensor\":2"\
   "}"\
"}\r\n" ;
/* END JSON descriptor for the sensor data */

/* User modifiable function. Update the below function
 * to initialize and setup I2C sensors */
void sensor_ssss_configure(void)
{
  /** @todo Replace contents of this function */
  sensor_ssss_config.rate_hz = SENSOR_SSSS_SAMPLE_RATE_HZ;
  sensor_ssss_config.n_channels = SENSOR_SSSS_CHANNELS_PER_SAMPLE;
  sensor_ssss_config.bit_depth = SENSOR_SSSS_BIT_DEPTH;
  sensor_ssss_config.sensor_id = SENSOR_SSSS_ID;
  static int sensor_ssss_configured = false;

  /*--- BEGIN User modifiable section ---*/
  // qorc_ssi_accel.begin();
  // qorc_ssi_accel.set_sample_rate(sensor_ssss_config.rate_hz);
  // qorc_ssi_accel.set_sample_resolution(sensor_ssss_config.bit_depth);
  // qorc_ssi_accel.set_mode(MC3635_MODE_CWAKE);
  
  qorc_ssi_adc.begin();
  qorc_ssi_adc.setSampleRate(sensor_ssss_config.rate_hz);
  
  /*--- END of User modifiable section ---*/

  if (sensor_ssss_configured == false)
  {
    sensor_ssss_configured = true;
  }
  sensor_ssss_startstop(1);
}

/* User modifiable function. Update the below function
 * to read sensor data sample from sensors and fill-in
 * the data block with these sensor data values */
int  sensor_ssss_acquisition_buffer_ready()
{
    int8_t *p_dest = (int8_t *) &psensor_ssss_data_block_prev->p_data;
    int dataElementSize = psensor_ssss_data_block_prev->dbHeader.dataElementSize;
    int ret;
    int batch_size;

    p_dest += sizeof(int8_t)*sensor_ssss_samples_collected*dataElementSize;

    /* Read 1 sample per channel, Fill the sample data to p_dest buffer */

    /*--- BEGIN User modifiable section ---*/
    // xyz_t accel_data = qorc_ssi_accel.read();  /* Read accelerometer data from MC3635 */

    // /* Fill this accelerometer data into the current data block */
    // int16_t *p_accel_data = (int16_t *)p_dest;

    // *p_accel_data++ = accel_data.x;
    // *p_accel_data++ = accel_data.y;
    // *p_accel_data++ = accel_data.z;
    
    int16_t *p_adc_data = (int16_t *)p_dest;

    int16_t channel_A0 = qorc_ssi_adc.getSingleEnded(0); //MQ7
    int16_t channel_A1 = qorc_ssi_adc.getSingleEnded(1); //Soil Moisture
    int16_t channel_A2 = qorc_ssi_adc.getSingleEnded(2); //Temperature

    float mv = ( channel_A2/4096.0)*3300; //4096 as ADS1015 is 12 bit
    float cel = mv/10;
    channel_A2 = (int16_t)( cel * 1.8 + 32);

  
      *p_adc_data++ = channel_A0;
      *p_adc_data++ = channel_A1;
      *p_adc_data++ = channel_A2;
    
      p_dest += 6; // advance datablock pointer to retrieve and store next sensor data
    
    
    

    /* Read data from other sensors */
    int bytes_to_read = SENSOR_SSSS_CHANNELS_PER_SAMPLE * (SENSOR_SSSS_BIT_DEPTH/8) ;

    sensor_ssss_samples_collected += SENSOR_SSSS_CHANNELS_PER_SAMPLE;
    batch_size = sensor_ssss_batch_size_get() * SENSOR_SSSS_CHANNELS_PER_SAMPLE;

    // return 1 if batch_size of samples are collected
    // return 0 otherwise
    if (sensor_ssss_samples_collected >= batch_size)
    {
      psensor_ssss_data_block_prev->dbHeader.numDataElements = sensor_ssss_samples_collected;
      psensor_ssss_data_block_prev->dbHeader.numDataChannels = SENSOR_SSSS_CHANNELS_PER_SAMPLE;
      sensor_ssss_samples_collected = 0;
      return 1;
    }
    else
    {
      return 0;
    }
}

/** End of User modifiable code and variable */

/*========== BEGIN: SSSS SENSOR Datablock processor definitions =============*/
/** @addtogroup QAI_SSSS_PIPELINE_EXAMPLE QORC SDK SSSS pipeline example
 *
 *  @brief SSSS pipeline example code
 *
 * This example code demonstrates setting up SSSS Queues,
 * setting up the datablock buffer manager (\ref DATABLK_MGR)
 * and setting up the datablock processor processing elements (\ref DATABLK_PE).
 * A specific SSSS processing element for motion detection is provided in this
 * example.
 *
 * @{
 */

/** Maximum number of ssss data blocks that may be queued for chain processing */
#define SENSOR_SSSS_NUM_DATA_BLOCKS              (SENSOR_SSSS_MAX_DATA_BLOCKS)

/** maximum number of vertical (parallel processing elements) that may generate datablock outputs
 *  that may add to the front of the queue.
 *
 *  Queue size of a given datablock processor must be atleast
 *  summation of maximum datablocks of all sensors registered for
 *  processing with some room to handle the vertical depth
 */
#define MAX_THREAD_VERTICAL_DEPTH_DATA_BLOCKS  (5)

#define SENSOR_SSSS_DBP_THREAD_Q_SIZE   (SENSOR_SSSS_MAX_DATA_BLOCKS+MAX_THREAD_VERTICAL_DEPTH_DATA_BLOCKS)
#define SENSOR_SSSS_DBP_THREAD_PRIORITY (10)

uint8_t               sensor_ssss_data_blocks[SENSOR_SSSS_MEMSIZE_MAX] ; //PLACE_IN_SECTION("HWA");
QAI_DataBlockMgr_t    sensor_ssssBuffDataBlkMgr;
QueueHandle_t         sensor_ssss_dbp_thread_q;

/* SSSS AI processing element */
extern void sensor_ssss_ai_data_processor(
       QAI_DataBlock_t *pIn,
       QAI_DataBlock_t *pOut,
       QAI_DataBlock_t **pRet,
       datablk_pe_event_notifier_t *pevent_notifier
     );
extern void sensor_ssss_ai_config(void *pDatablockManagerPtr);
extern int  sensor_ssss_ai_start(void);
extern int  sensor_ssss_ai_stop(void);

/** Sensor SSSS AI processing element functions */

datablk_pe_funcs_t sensor_ssss_sensiml_ai_funcs =
{
  .pconfig     = sensor_ssss_ai_config,
  .pprocess    = sensor_ssss_ai_data_processor,
  .pstart      = sensor_ssss_ai_start,
  .pstop       = sensor_ssss_ai_stop,
  .p_pe_object = (void *)NULL
} ;

/** outQ processor for SSSS AI processing element */
outQ_processor_t sensor_ssss_sensiml_ai_outq_processor =
{
  .process_func = NULL,
  .p_dbm = &sensor_ssssBuffDataBlkMgr,
  .in_pid = SENSOR_SSSS_AI_PID,
  .outQ_num = 0,
  .outQ = NULL,
  .p_event_notifier = NULL
};

/* SSSS Live-stream processing element */
extern void sensor_ssss_livestream_data_processor(
       QAI_DataBlock_t *pIn,
       QAI_DataBlock_t *pOut,
       QAI_DataBlock_t **pRet,
       datablk_pe_event_notifier_t *pevent_notifier
     );
extern void sensor_ssss_livestream_config(void *pDatablockManagerPtr);
extern int  sensor_ssss_livestream_start(void);
extern int  sensor_ssss_livestream_stop(void);

/** Sensor SSSS AI processing element functions */

datablk_pe_funcs_t sensor_ssss_livestream_funcs =
{
  .pconfig     = sensor_ssss_livestream_config,
  .pprocess    = sensor_ssss_livestream_data_processor,
  .pstart      = sensor_ssss_livestream_start,
  .pstop       = sensor_ssss_livestream_stop,
  .p_pe_object = (void *)NULL
} ;

/** outQ processor for SSSS Live-stream processing element */
outQ_processor_t sensor_ssss_livestream_outq_processor =
{
  .process_func = NULL,
  .p_dbm = &sensor_ssssBuffDataBlkMgr,
  .in_pid = SENSOR_SSSS_LIVESTREAM_PID,
  .outQ_num = 0,
  .outQ = NULL,
  .p_event_notifier = NULL
};

datablk_pe_descriptor_t  sensor_ssss_datablk_pe_descr[] =
{ // { IN_ID, OUT_ID, ACTIVE, fSupplyOut, fReleaseIn, outQ, &pe_function_pointers, bypass_function, pe_semaphore }
#if (SENSOR_SSSS_RECOG_ENABLED)
    /* processing element descriptor for SensiML AI for SSSS sensor */
    { SENSOR_SSSS_ISR_PID, SENSOR_SSSS_AI_PID, true, false, true, &sensor_ssss_sensiml_ai_outq_processor, &sensor_ssss_sensiml_ai_funcs, NULL, NULL},
#endif

#if (SENSOR_SSSS_LIVESTREAM_ENABLED)
    /* processing element descriptor for SSSS sesnsor livestream */
    { SENSOR_SSSS_ISR_PID, SENSOR_SSSS_LIVESTREAM_PID, true, false, true, &sensor_ssss_livestream_outq_processor, &sensor_ssss_livestream_funcs, NULL, NULL},
#endif
};

datablk_processor_params_t sensor_ssss_datablk_processor_params[] = {
    { SENSOR_SSSS_DBP_THREAD_PRIORITY,
      &sensor_ssss_dbp_thread_q,
      sizeof(sensor_ssss_datablk_pe_descr)/sizeof(sensor_ssss_datablk_pe_descr[0]),
      sensor_ssss_datablk_pe_descr,
      256*2,
      (char*)"SENSOR_SSSS_DBP_THREAD",
      NULL
    }
};

void sensor_ssss_block_processor(void)
{
  /* Initialize datablock manager */
   datablk_mgr_init( &sensor_ssssBuffDataBlkMgr,
                      sensor_ssss_data_blocks,
                      sizeof(sensor_ssss_data_blocks),
                      (SENSOR_SSSS_SAMPLES_PER_BLOCK),
                      ((SENSOR_SSSS_BIT_DEPTH)/8)
                    );

  /** SSSS datablock processor thread : Create SSSS Queues */
  sensor_ssss_dbp_thread_q = xQueueCreate(SENSOR_SSSS_DBP_THREAD_Q_SIZE, sizeof(QAI_DataBlock_t *));
  vQueueAddToRegistry( sensor_ssss_dbp_thread_q, "SENSOR_SSSSPipelineExampleQ" );

  /** SSSS datablock processor thread : Setup SSSS Thread Handler Processing Elements */
  datablk_processor_task_setup(&sensor_ssss_datablk_processor_params[0]);

  /** Set the first data block for the ISR or callback function */
  sensor_ssss_set_first_data_block();

  /* [TBD]: sensor configuration : should this be here or after scheduler starts? */
  sensor_ssss_add();
  sensor_ssss_configure();
#if 0
  printf("Sensor Name:                   %s\n", "SENSOR_SSSS_NAME");
  printf("Sensor Memory:                 %d\n", (int)SENSOR_SSSS_MEMSIZE_MAX);
  printf("Sensor Sampling rate:          %d Hz\n", (int)SENSOR_SSSS_SAMPLE_RATE_HZ);
  printf("Sensor Number of channels:     %d\n", (int)SENSOR_SSSS_CHANNELS_PER_SAMPLE);
  printf("Sensor frame size per channel: %d\n", (int)SENSOR_SSSS_SAMPLES_PER_CHANNEL);
  printf("Sensor frame size:             %d\n", (int)SENSOR_SSSS_SAMPLES_PER_BLOCK);
  printf("Sensor sample bit-depth:       %d\n", (int)SENSOR_SSSS_BIT_DEPTH);
  printf("Sensor datablock count:        %d\n", (int)SENSOR_SSSS_NUM_DATA_BLOCKS);
#endif
}
/*========== END: SSSS SENSOR Datablock processor definitions =============*/

/* BEGIN timer task related functions */
TimerHandle_t sensor_ssss_TimId ;
extern "C" void sensor_ssss_dataTimer_Callback(TimerHandle_t hdl);
void sensor_ssss_dataTimer_Callback(TimerHandle_t hdl)
{
  // Warning: must not call vTaskDelay(), vTaskDelayUntil(), or specify a non zero
  // block time when accessing a queue or a semaphore.
  sensor_ssss_acquisition_read_callback(); //osSemaphoreRelease(readDataSem_id);
}
void sensor_ssss_dataTimerStart(void)
{
  BaseType_t status;
  TimerCallbackFunction_t xCallback = sensor_ssss_dataTimer_Callback;
#if (USE_SENSOR_SSSS_FIFO_MODE)
  // setup FIFO mode
#else
  int milli_secs = (1000 / SENSOR_SSSS_SAMPLE_RATE_HZ); // reads when a sample is available (upto 416Hz)
#endif

  // Create periodic timer
  if (!sensor_ssss_TimId) {
    sensor_ssss_TimId = xTimerCreate("SensorSSSSTimer", pdMS_TO_TICKS(milli_secs), pdTRUE, (void *)0, xCallback);
    configASSERT(sensor_ssss_TimId != NULL);
  }

  if (sensor_ssss_TimId)  {
    status = xTimerStart (sensor_ssss_TimId, 0);  // start timer
    if (status != pdPASS)  {
      // Timer could not be started
    }
  }
#if (USE_SENSOR_SSSS_FIFO_MODE)
  // setup FIFO mode
#endif
  // start the sensor
}

void sensor_ssss_dataTimerStop(void)
{
  if (sensor_ssss_TimId) {
    xTimerStop(sensor_ssss_TimId, 0);
  }
  // stop the sensor
}

/* END timer task related functions */

/* BEGIN Sensor Generic Configuration */

sensor_generic_config_t sensor_ssss_config;

void sensor_ssss_startstop( int is_start )
{
  /** @todo Replace contents of this function */
  if ((is_start) && (sensor_ssss_config.enabled) && (sensor_ssss_config.is_running == 0) )
  {
     sensor_ssss_dataTimerStart();
     sensor_ssss_config.is_running = 1;
  }
  else if ( (is_start == 0) && (sensor_ssss_config.is_running == 1) )
  {
    sensor_ssss_dataTimerStop();
    sensor_ssss_config.is_running = 0;
  }
}

void sensor_ssss_clear( void )
{
  sensor_ssss_config.enabled = false;
  /** @todo Replace contents of this function */
}

void sensor_ssss_add(void)
{
  sensor_ssss_config.enabled = true;
  /** @todo Replace contents of this function */
}
/* End of Sensor Generic Configuration */

/* BEGIN SSSS Acquisition */
/* Sensor SSSS capture ISR */
#define SENSOR_SSSS_ISR_EVENT_NO_BUFFER  (1)   ///< error getting a new datablock buffer

#define SSSS_ISR_OUTQS_NUM        (1)
QueueHandle_t   *sensor_ssss_isr_outQs[SSSS_ISR_OUTQS_NUM] = { &sensor_ssss_dbp_thread_q };
QAI_DataBlock_t *psensor_ssss_data_block_prev = NULL;
int              sensor_ssss_samples_collected = 0;

outQ_processor_t sensor_ssss_isr_outq_processor =
{
  .process_func = sensor_ssss_acquisition_read_callback,
  .p_dbm = &sensor_ssssBuffDataBlkMgr,
  .in_pid = SENSOR_SSSS_ISR_PID,
  .outQ_num = 1,
  .outQ = sensor_ssss_isr_outQs,
  .p_event_notifier = NULL
};

void sensor_ssss_set_first_data_block()
{
  /* Acquire a datablock buffer */
  if (NULL == psensor_ssss_data_block_prev)
  {
    datablk_mgr_acquire(sensor_ssss_isr_outq_processor.p_dbm, &psensor_ssss_data_block_prev, 0);
  }
  configASSERT(psensor_ssss_data_block_prev); // probably indicates uninitialized datablock manager handle
  sensor_ssss_samples_collected = 0;
  psensor_ssss_data_block_prev->dbHeader.Tstart = xTaskGetTickCount();
}

int sensor_ssss_batch_size_get(void)
{
  return (SENSOR_SSSS_SAMPLES_PER_CHANNEL);
}

void sensor_ssss_acquisition_read_callback(void)
{
    int gotNewBlock = 0;
    QAI_DataBlock_t  *pdata_block = NULL;

    if (!sensor_ssss_acquisition_buffer_ready())
    {
      return;
    }
    /* Acquire a new data block buffer */
    datablk_mgr_acquire(sensor_ssss_isr_outq_processor.p_dbm, &pdata_block, 0);
    if (pdata_block)
    {
        gotNewBlock = 1;
    }
    else
    {
        // send error message
        // xQueueSendFromISR( error_queue, ... )
        if (sensor_ssss_isr_outq_processor.p_event_notifier)
          (*sensor_ssss_isr_outq_processor.p_event_notifier)(sensor_ssss_isr_outq_processor.in_pid, SENSOR_SSSS_ISR_EVENT_NO_BUFFER, NULL, 0);
        pdata_block = psensor_ssss_data_block_prev;
        pdata_block->dbHeader.Tstart = xTaskGetTickCount();
        pdata_block->dbHeader.numDropCount++;
    }

    if (gotNewBlock)
    {
        /* send the previously filled ssss data to specified output Queues */
        psensor_ssss_data_block_prev->dbHeader.Tend = pdata_block->dbHeader.Tstart;
        datablk_mgr_WriteDataBufferToQueues(&sensor_ssss_isr_outq_processor, NULL, psensor_ssss_data_block_prev);
        psensor_ssss_data_block_prev = pdata_block;
    }
}
/* END SSSS Acquisition */

/* SSSS AI processing element functions */
void sensor_ssss_ai_data_processor(
       QAI_DataBlock_t *pIn,
       QAI_DataBlock_t *pOut,
       QAI_DataBlock_t **pRet,
       datablk_pe_event_notifier_t *pevent_notifier
     )
{
    int16_t *p_data = (int16_t *) ( (uint8_t *)pIn + offsetof(QAI_DataBlock_t, p_data) );

    // Invoke the SensiML recognition API
    int nSamples = pIn->dbHeader.numDataElements;
    int nChannels = pIn->dbHeader.numDataChannels;

    int batch_sz = nSamples / nChannels;
    sml_recognition_run_batch(p_data, batch_sz, nChannels, sensor_ssss_config.sensor_id);
    *pRet = NULL;
    return;
}

void sensor_ssss_ai_config(void *pobj)
{
}

int  sensor_ssss_ai_start(void)
{
  return 0;
}

int  sensor_ssss_ai_stop(void)
{
  return 0;
}

void sensor_ssss_event_notifier(int pid, int event_type, void *p_event_data, int num_data_bytes)
{
  char *p_data = (char *)p_event_data;
  printf("[SSSS Event] PID=%d, event_type=%d, data=%02x\n", pid, event_type, p_data[0]);
}

/* SSSS livestream processing element functions */

void sensor_ssss_livestream_data_processor(
       QAI_DataBlock_t *pIn,
       QAI_DataBlock_t *pOut,
       QAI_DataBlock_t **pRet,
       datablk_pe_event_notifier_t *pevent_notifier
     )
{
    int16_t *p_data = (int16_t *) ( (uint8_t *)pIn + offsetof(QAI_DataBlock_t, p_data) );
    //struct sensor_data sdi;
    uint64_t  time_start, time_curr, time_end, time_incr;

    if (sensor_ssss_config.enabled == true)
    {
      // Live-stream data to the host
      uint8_t *p_source = pIn->p_data ;
      int ilen = pIn->dbHeader.numDataElements * pIn->dbHeader.dataElementSize ;
#if (SENSOR_SSSS_RATE_DEBUG_GPIO == 1)
      // Toggle GPIO to indicate that a new datablock buffer is dispatched to UART
      // for transmission for data collection
      HAL_GPIO_Write(GPIO_2, sensor_rate_debug_gpio_val);
      sensor_rate_debug_gpio_val ^= 1;
#endif
      ssi_publish_sensor_data(p_source, ilen);
    }
    *pRet = NULL;
    return;
}

void sensor_ssss_livestream_config(void *pobj)
{
}

int  sensor_ssss_livestream_start(void)
{
  return 0;
}

int  sensor_ssss_livestream_stop(void)
{
  return 0;
}

Credits

Mithun Das

Mithun Das

33 projects • 170 followers
Hacker and Maker driven by passion. Ambassador at Edge Impulse and Balena. Follow me on Twitter @_mithundas

Comments