|
SensiML Analytics Studio |
|
|
SensiML Data Capture Lab |
|
|
QORC SDK |
|
|
Helium Console |
Protect Peatlands for people and planet with help of SensiML
The 1997 Indonesia fire burned 9.7– 11.7 million ha on Borneo and Sumatra, destroying 4.5–6 million ha of species rich Dipterocarp forest (including 1.5–2.1 million ha on peat soils). Estimated carbon emissions from these 1997-98 fires on peatlands were 0.81–2.57 Pg, equivalent to 13–40% of annual global fossil fuel emissions [source internet ]
Peatlands and climate change
Peatlands are a type of wetlands which are among the most valuable ecosystems on Earth. They are critical for preserving global biodiversity, provide safe drinking water, minimize flood risk and help address climate change.
Peatlands are the largest natural terrestrial carbon store. The area covered by near natural peatland worldwide ( >3 million square km) sequesters 0.37 giga tons of carbon dioxide (CO2) a year – storing more carbon than all other vegetation types in the world combined.
Damaged peatlands are a major source of greenhouse gas emissions, annually releasing almost 6% of global CO2 emissions. Peatland restoration can therefore bring significant emissions reductions.
Importance Of Peatlands
Peat swamp forests are lowland vegetation in tropical peatlands where most of the flora and fauna are uniquely adapted to the environment – i.e waterlogged and acidic water. Peat swamps are important for many reasons, mainly as a source of fresh water, flood mitigation, carbon sink and store, and to safeguard biodiversity.
Unfortunately, Western Europe has lost nearly 90% of its peatlands while Central Europe only has 50% of functioning peatlands. In Asia, 70% of peat swamps have been lost. Now, more than ever, safeguarding and rehabilitating peatlands are an important effort for the good of the planet and its inhabitants.
1. Peatlands as carbon sinks
One very surprising fact about peatlands is that these forests store about 30% of all carbon on land. That’s about 500 billion tonnes of carbon! Peatlands in Southeast Asia alone hold 14%, or approximately, 68 billion tonnes of carbon in the world. Making sure the carbon in peatlands go undisturbed is the key to regulating climate as improperly managed peatlands can cause a massive release of carbon into the air, which even basic science will tell you is very, very bad.
2. Peatlands and water regulation
Apart from storing vast amounts of carbon, peatlands consists of approximately 90% of water in soil, stored amongst the decaying, porous organic matter (dried leaves, etc). This gives the forest floor a muddy quality typically associated with swamps and bogs, both different types of peatlands.
Healthy, wet peatlands absorb pollutants from the atmosphere, including nitrogen, sulphur dioxide, heavy metals and carbon. These pollutants are what make peat soil acidic. Since the pollutants are absorbed into the soil, water that flows out of peatlands into the ground or rivers come out clean – like a natural filtration system.
When peatlands are drained, it will not be able to regulate pollutants in the atmosphere as well as it should, and that will result in an increase of carbon in water. Water with high carbon content will need to be treated before it can be safely used, and the treatment process is both expensive and results in harmful by-products.
Apart from regulating pollutants, peatlands also regulate excess water. Peatlands can reduce downstream flooding as it acts like a sponge to soak up excess water. And in dry seasons, peatlands slowly release water, thus ensuring a source of fresh water for surrounding communities. Peatlands are great water reservoirs for irrigation, hydro-electricity, recreation, aquatic farming, and as a source of clean water.
Drainage Process
Reasons Behind Draining
It's quite evident that draining of peatland is a serious problem and has direct impact on climate change. Though some peatlands are draining naturally but in many countries they are draining because of us.
In Southeast Asia, peatlands have been drained for conversion to plantations (for palm oil and paper pulp); in Europe and North America, lowland peatlands have been drained for vegetables, cereals and livestock pastures.
Agriculture, forestry and mining have so far impacted about 25% of the peatlands on Earth. While large parts of the enormous peatlands of North America and Russia are still relatively intact, in many parts of Europe, Central and Southeast Asia, Argentina and Chile, peatlands have been significantly degraded. There is increasing interest in protecting and restoring peatlands in order to conserve existing C stocks, help mitigate climate change, and preserve ecosystem benefits.
Drainage And Peat Fire
As you have seen in drainage process above, dry peatlands are prone to catch fire and accelerates the drainage process.
When peatlands are drained, they become highly vulnerable to peat fire. A simple discarded cigarette butt or a match can cause huge devastation, degradation of the resource base, accelerated carbon emission and a host of health issues for local communities. Dry peat ignites very easily and can burn for days or weeks, even smoldering underground and reemerging away from the initial source. This makes these fires incredibly difficult to extinguish, and highly unpredictable and uncontrollable.
How AI Can Help To Protect Peatlands?
If you are following the article so far, you have realized dry peatlands are prone to fire. When fire is set or even when peatlands are drying, it releases all the CO2 into atmosphere. Sometimes human set fire intentionally to burn the peatlands to expand farming lands. AI can help to predict when peatlands are drying or set on fire collecting sensor data such as Soil Moisture level, CO2 emission in the air, increase in Temperature and then analyzing these data to predict the situation such as drought, fire etc.
Based on the early prediction, we can take some preventive & proactive measures such as put out the fire before it burns the peatland or rewet the peatlands when it's drying.
Peat Guard
Introducing Peat Guard - A Solar powered device running tinyML model on Quick Feather board equipped with a Soil Moisture Sensor, Analog Temperature sensor and MQ7 CO2 gas sensor. The has a LoRa node - Heltec Wireless Stick Life which sends UPLINK data to Helium, The People Network which feeds data to Ubidots dashboard.
I am going to take a deep dive from here to explain different components of my device in detail.
Quick Feather Board And SensiML
SensiML Analytics Toolkit suite automates each step of the process for creating optimized AI IoT sensor recognition code. The overall workflow uses a growing library of advanced ML and AI algorithms to generate code that can learn from new data either the development phase or once deployed.
I strongly recommend you to visit https://sensiml.com/ website and watch the workflow video which would give you a pretty good overview of SensiML software suit.
QuickFeather is based on open source hardware, compatible with the Adafruit Feather form factor, and is built around 100% open source software (including the Symbiflow FPGA Tools).
The QuickFeather is powered by QuickLogic’s EOS? S3, the first FPGA-enabled Arm Cortex?-M4F MCU to be fully supported with Zephyr RTOS. Other functionality includes:
- GigaDevice 16-Mbit of flash memory #GD25Q16CEIGR
- mCube MC3635 accelerometer
- Infineon DPS310 pressure sensor
- Infineon IM69D130 PDM digital microphone
- Powered from USB or a single Li-Po battery
- Integrated battery charger
- USB data signals tied to programmable logic
Visit https://www.quicklogic.com/products/eos-s3/quickfeather-development-kit/ for more information on this board.
Data Collection Through SensiML Data Capture Lab (DCL)
Before you start collecting data, you need to connect your I2C sensor to Quick Feather board and make some modification in software. You will find my code in the code section of this project. I recommend you to visit this tutorial page where you will very useful tutorials to get started with new I2C sensor and data collection process.
Image: Simulating dry peatland
Image: Simulating Peatland on fire
You can see in above images how I hooked up 3 sensors with Quick Feather board and start collecting data. I have stacked QF on top of Huzzah ESP32 so that I can capture data over WiFi.
qorc_ssi_adc.begin();
qorc_ssi_adc.setSampleRate(sensor_ssss_config.rate_hz);
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
Train Your Model Using SensiML Analytic Studio
Once you have collected enough data and segmented, head over to Analytic Studio to train your model.
Integrate Knowledge Pack With Firmware
Follow this nice tutorial on youtube posted by Chris Knorowski or follow below simple steps.
Download the knowledge pack from SesniML analytic tool.
Copy entire qf_ssi_ai_app as qf_ssi_ai_myapp (don't want to mess existing app )
Go inside qf_ssi_ai_myapp folder and delete knowledgepack folder
Unzip the download KP and you will see a folder named knowledgepack_project. Copy this folder to qf_ssi_ai_myapp and rename to knowledgepack
Open sensor_ssss.h file and set SENSOR_SSSS_RECOG_ENABLED to 1 and SENSOR_SSSS_LIVESTREAM_ENABLED to 0
Delete output folder from qf_ssi_ai_myapp
Build the program using make command
This will generate output/bin/qf_ssi_ai_myapp.bin file
Flash the file and you are good to go!
Troubleshooting
If you are seeing `dcl_commands.h: No such file or directory` while compiling KP library, you are on same page as I was. You need to remove that header file and include "sensor_ssss.h" in sml_recognition_run.c file. The methods sml_recognition_run_batch and sml_recognition_run_single may come with empty implementation. You need to provide implementation. Checkout code section for a reference. Checkout below link for further information
https://sensiml.com/documentation/knowledge-packs/building-a-knowledge-pack-library.html
After you compile and flash the bin to your QF board, you may connect to SensiML gateway app to test recognition.
Send Data To Helium Network
Helium the world's first peer-to-peer wireless network that provides a secure and cost-effective way for low-power Internet of Things devices to send data to and from the Internet. The network enables companies to focus on applications and use cases, not worry about cellular plans for devices or managing network infrastructure. To quickly compare the cost, let's take an example.
To send GPS coordinates every minute would cost you only 43centsper month! Yes, only 43 cents compared to > $5 using regular GSM.
I have used low power Heltec Wireless Stick Life as LoRa node which is sending data to helium network every 2 minutes. See code section below for Arduino sketch. Also checkout my project for more information about how to get started with helium network.
Helium Console
Integrate Helium With Ubidots
It's nice to see data coming to Helium console but what would you do with that? You need some sort of dashboard to visualize the data and prediction. I have decided to use Ubidots STEM which is free and most elegant dashboard solution I came across so far.
Sending data from Helium console to Ubidots is very simple and straight forward. Follow this tutorial.
Once important thing is to decode the message before sending to Ubidots. I have created a decoder function in helium console to extract data from byte array and convert to JSON format.
function Decoder(bytes, port) {
var clas = bytes[0] | bytes[1] << 8;
var lat = ( bytes[2] | bytes[3] << 8 | bytes[4] << 16 | (bytes[4] & 0x80 ? 0xFF << 24 : 0)) / 10000;
var lon = ( bytes[5] | bytes[6] << 8 | bytes[7] << 16 | (bytes[7] & 0x80 ? 0xFF << 24 : 0)) / 10000;
var temperature = (bytes[8] | bytes[9] << 8);
var moisture = bytes[10] | bytes[11] << 8;
var pressure = bytes[12] | bytes[13] << 8;
var gas = bytes[14] | bytes[15] << 8;
var bat = (bytes[16] | bytes[17] << 8)/1000;
bat = bat * 2.3857143;
var decoded = {
clas: 0,
temperature: temperature,
moisture: moisture,
gas: gas,
bat:bat,
position:{
latitude: lat,
longitude: lon
}
};
return decoded;
}
It's all good to visualize data on Ubidots dashboard. But who is going to constantly stare at the screen and act when there is a fire predicted? Here is the good news! Ubidots can send real time alerts via email, text or even slack.
Watch Demo
X-Factors
1. Low Power Consumption
Heltec Lora node wakes up for 10s every 2 minutes and powers the sensors and QF board. This saves power significantly. Using 5000mA LiPo battery, it can run the device for few days.
QF powered by Heltec 3.3V output
2. Solar Power
Along with low power consumption, LiPo battery is recharged by solar energy during day time which make the device run for a very long period of time without replacing the battery or plug into any power outlet.
3. No Cellular / WiFi
No need for cellular or wifi connectivity, which reduced the data transfer cost significantly. This device uses LoRaWan protocol to send data to Helium Network.
#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;
}
//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;
}
#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__
/** @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;
}
Protect Peatlands for people and planet with help of SensiML
*PCBWay community is a sharing platform. We are not responsible for any design issues and parameter issues (board thickness, surface finish, etc.) you choose.
- Comments(0)
- Likes(2)
- Ömer KANOĞLU Feb 19,2022
- Duncan Hames Nov 24,2021
- 1 USER VOTES
- YOUR VOTE 0.00 0.00
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
8design
-
8usability
-
9creativity
-
9content
More by mithundotdas
- Mahout - Save The Elephants ?? Save The ElephantsLet's work together and save the beautiful elephants using cutting edge technol...
- Smart Indoor Harvesting Using Wio Terminal & Blynk MotivationThis project is basically designed and programmed by my daughter, Sashrika with little hel...
- A. Eye - Watch out for vehicles This is a getting started guide for beginners to Intel Compute Stick and OpenVINO. I will be using p...
- Touchless ATM using Augmented Reality Stop The SpreadPrevent spreading of COVID-19 is very critical to flatten the curve. Research has fou...
- Basement Monitoring Using Wio Terminal and Blynk MotivationI live in East Coast of USA where most of the houses have basements, some are finished, so...
- Buddy - A personal home office assistance What Problem Are We Talking About ?Corona virus out-break has put our lives upside dow, creating new...
- AI powered thermal camera for safe camping The ProblemI like camping but I am not a "camping enthusiast", I usually camp once or twice a year w...
- Pet Activity Tracker using XIAO BLE Sense & Edge Impulse StoryWhy should human have all the fitness trackers? Our pets deserve more to stay active. I am usin...
- Protect Peatlands for people and planet with help of SensiML The 1997 Indonesia fire burned 9.7– 11.7 million ha on Borneo and Sumatra, destroying 4.5–6 million ...
-
-
-
3D printed Enclosure Backplate for Riden RD60xx power supplies
80 1 1 -
-
-
-
Sega Master System RGB Encoder Switcher Z80 QSB v1.2
71 0 0 -
18650 2S2P Battery Charger, Protection and 5V Output Board
107 0 0 -
High Precision Thermal Imager + Infrared Thermometer | OpenTemp
525 0 7 -
Sony PlayStation Multi Output Frequency Oscillator (MOFO) v1
147 0 2 -