Introduction
I am going to use Machine Learning for detecting the electrical load. Power, Current & Voltage will be used as a dataset and the attached load will be identified analyzing the current, voltage, and power waveform. I will use the SensiML platform for detecting and identifying the connected load.
What is SensiML?
SensiML offers cutting-edge development software that transforms low-power IoT endpoints into intelligent AI devices rapidly reducing data science complexity. Compact algorithms can be created that run on small IoT devices and not in the cloud. Collect precise, traceable, and version-controlled datasets. Advanced AutoML code-gen is used to quickly create autonomous working device code. You can choose your interface and level of AI expertise. All aspects of your algorithm will remain accessible to you. Edge tuning models can be built that adapt to the data they receive. SensiML Analytics Toolkit suite automates every step of the process to create optimized AI IoT sensor recognition codes. The workflow employs a growing number of advanced ML algorithms and AI algorithms to generate code that can learn new data, either in the development phase or once it is deployed. SensiML can be effectively used by teams with or without data science expertise to rapidly build intelligent IoT endpoint devices. The toolkit supports "Arm Cortex-M class and higher microcontroller cores, Intel x86 instruction set processors, and heterogeneous core QuickLogic SoCs and QuickAI platforms with FPGA optimizations". The toolkit provides the equivalent functionality to what we do with Edge Impulse for TinyML.
SensiML enables developers to build production-grade smart sensor devices faster, tinier, better, and more efficiently than ever before. SensiMl also provides a data collection tool named Data Capture Lab.
Data Collection using Data Capture Lab (DCL)
Data Capture Lab is a full-fledged, time-series sensor data collection and labeling tool that brings a level of automated dataset management developers are accustomed to in programming tools but is sorely missing in edge ML software until now. SensiML’s approach focuses on allowing developers to build datasets as enduring intellectual property (IP) that can be maintained, modified, explored, extended, and exported easily as required. And good data feeding the modeling process translates to good ML inference code as output.
The Data Capture Lab is an application that helps you capture, organize, and label raw data from the sensor and transform it into the events you want to detect. The Data Capture Lab can import sensor data from any external source if it is in a CSV, WAV, or QLSM format. You can import pre-labeled sensor data from outside sources into the SensiML Toolkit. It is a very useful tool if you have created your own protocols/applications for collecting/labeling sensor data and wish to integrate your labeled data into the SensiML Toolkit. The Data Capture Lab also supports data collection from any third-party hardware. We will use our PSoC 6 Kit for capturing sensor data.
For capturing data we need to:
1. Develop a firmware to serial data transfer from our device to data capture lab
2. Create a Device Plugin. A Device Plugin is created by implementing an .SSF (SensiML Sensor Format) file. I added the .SSF file in a zip for our case herewith. If you want to know more please visit https://sensiml.com/documentation/data-capture-lab/adding-custom-device-firmware.html .
Developing Firmware for Data Collection using DCL
The firmware will collect sensor data, process the data, and send the data to DCL using UART following a specific format. Before sending the actual data we need to send a configuration string to DCL to define how our output data will arrive at the Data Capture Lab and it is done using a simple JSON format. This lays out the sample rate and column order of data that will be streaming from a device. The configuration JSON will look like as follows:
{ "sample_rate":476, "version":1, "samples_per_packet":3, "column_location":{ "Voltage":0, "Current":1, "Power":2 } }
The version should be either 1 or 2. Version 2 supports a sync protocol with a simple CRC for data integrity. If no version is supplied, the Data Capture Lab will assume version 1. The sample rate is the first item defined. Currently, this specification supports one sample rate at a time. Sensors that run slower, but are included in this list, are expected to be over-sampled to match the highest sample rate. Column Location describes both the name of the given sensor column and its location in the output data stream.
Upon receiving the JSON and connection, the Data Capture Lab will send a “connect” string back to the device. This should cause the device to start streaming out data. The complete firmware is as follows:
#include "cy_pdl.h" #include "cyhal.h" #include "cybsp.h" #include "cy_retarget_io.h" #define BAUD_RATE 115200 //#define BAUD_RATE 230400 #define CONFIG_BUF_SIZE 200 /* ADC input pin */ #define VOLTAGE_CHANNEL (P10_0) #define CURRENT_CHANNEL (P10_1) /* Number of scans every time ADC read is initiated */ #define NUM_SCAN (1) ///String inputString = ""; // a string to hold incoming data bool config_receive = false; uint8_t read_data; /* Variable to store the received character through terminal */ /* ADC Channel constants*/ enum ADC_CHANNELS { CHANNEL_0 = 0, CHANNEL_1, NUM_CHANNELS } adc_channel; void uart_receive_data(void); bool check_received_string(void); bool check_received_string_disconnect(void); void send_config_message(void); void send_sensor_data(int16_t voltage, int16_t current, int16_t power); void adc_multi_channel_init(void); void adc_multi_channel_process(void); static void adc_event_handler(void* arg, cyhal_adc_event_t event); void calculate_real_power_send_sensiml(int16_t no_of_sample); void handle_error(void) { /* Disable all interrupts. */ __disable_irq(); CY_ASSERT(0); } void append(char* s, char c) { int len = strlen(s); s[len] = c; s[len+1] = '\0'; } /******************************************************************************* * Global Variables *******************************************************************************/ /* ADC Object */ cyhal_adc_t adc_obj; /* ADC Channel 0 Object */ cyhal_adc_channel_t adc_chan_0_obj; /* ADC Channel 1 Object */ cyhal_adc_channel_t adc_chan_1_obj; /* Default ADC configuration */ const cyhal_adc_config_t adc_config = { .continuous_scanning=false, // Continuous Scanning is disabled .average_count=1, // Average count disabled .vref=CYHAL_ADC_REF_VDDA, // VREF for Single ended channel set to VDDA .vneg=CYHAL_ADC_VNEG_VSSA, // VNEG for Single ended channel set to VSSA .resolution = 12u, // 12-bit resolution .ext_vref = NC, // No connection .bypass_pin = NC }; // No connection /* Asynchronous read complete flag, used in Event Handler */ static bool async_read_complete = false; /* Variable to store results from multiple channels during asynchronous read*/ int32_t result_arr[NUM_CHANNELS * NUM_SCAN] = {0}; int main(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Initialize the device and board peripherals */ result = cybsp_init(); /* Board init failed. Stop program execution */ if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } /* Enable global interrupts */ __enable_irq(); /* Initialize retarget-io to use the debug UART port */ result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE); /* retarget-io init failed. Stop program execution */ if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } /* Initialize Channel 0 and Channel 1 */ adc_multi_channel_init(); /* Update ADC configuration */ result = cyhal_adc_configure(&adc_obj, &adc_config); if(result != CY_RSLT_SUCCESS) { printf("ADC configuration update failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } int16_t no_smaple = 50; for (;;) { if(!config_receive){ send_config_message(); //sensiml_connect_string(); cyhal_system_delay_ms(1000); if((int)cyhal_uart_readable(&cy_retarget_io_uart_obj)>0){ uart_receive_data(); if(check_received_string()){ config_receive = true; } } } else{ if((int)cyhal_uart_readable(&cy_retarget_io_uart_obj)>0){ uart_receive_data(); if(check_received_string_disconnect()){ config_receive = false; } } calculate_real_power_send_sensiml(no_smaple); } } } void adc_multi_channel_init(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Initialize ADC. The ADC block which can connect to pin 10[0] is selected */ result = cyhal_adc_init(&adc_obj, VOLTAGE_CHANNEL, NULL); if(result != CY_RSLT_SUCCESS) { printf("ADC initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* ADC channel configuration */ const cyhal_adc_channel_config_t channel_config = { .enable_averaging = false, // Disable averaging for channel .min_acquisition_ns = 1000, // Minimum acquisition time set to 1us .enabled = true }; // Sample this channel when ADC performs a scan /* Initialize a channel 0 and configure it to scan P10_0 in single ended mode. */ result = cyhal_adc_channel_init_diff(&adc_chan_0_obj, &adc_obj, VOLTAGE_CHANNEL, CYHAL_ADC_VNEG, &channel_config); if(result != CY_RSLT_SUCCESS) { printf("ADC voltage channel initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* Initialize a channel 1 and configure it to scan P10_0 in single ended mode. */ result = cyhal_adc_channel_init_diff(&adc_chan_1_obj, &adc_obj, CURRENT_CHANNEL, CYHAL_ADC_VNEG, &channel_config); if(result != CY_RSLT_SUCCESS) { printf("ADC current channel initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* Register a callback to handle asynchronous read completion */ cyhal_adc_register_callback(&adc_obj, &adc_event_handler, result_arr); /* Subscribe to the async read complete event to process the results */ cyhal_adc_enable_event(&adc_obj, CYHAL_ADC_ASYNC_READ_COMPLETE, CYHAL_ISR_PRIORITY_DEFAULT, true); printf("ADC is configured in multichannel configuration.\r\n\n"); printf("Voltage channel is configured in single ended mode, connected to pin \r\n"); printf("P10_0. Provide input voltage at P10_0\r\n"); printf("Current channel is configured in single ended mode, connected to pin \r\n"); printf("P10_1. Provide input voltage at P10_1\r\n"); //cyhal_system_delay_ms(200); } void adc_multi_channel_process(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Variable to store ADC conversion result from channel 0 */ int32_t adc_result_0 = 0; /* Variable to store ADC conversion result from channel 1 */ int32_t adc_result_1 = 0; /* Initiate an asynchronous read operation. The event handler will be called * when it is complete. */ result = cyhal_adc_read_async_uv(&adc_obj, NUM_SCAN, result_arr); if(result != CY_RSLT_SUCCESS) { printf("ADC async read failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* * Read data from result list, input voltage in the result list is in * microvolts. Convert it millivolts and print input voltage * */ adc_result_0 = result_arr[CHANNEL_0]/1000; adc_result_1 = result_arr[CHANNEL_1]/1000; printf("Voltage channel input: %4ldmV \t Current channel input: %4ldmV\r\n", (long int)adc_result_0, (long int)adc_result_1); /* Clear async read complete flag */ async_read_complete = false; } static void adc_event_handler(void* arg, cyhal_adc_event_t event) { if(0u != (event & CYHAL_ADC_ASYNC_READ_COMPLETE)) { /* Set async read complete flag to true */ async_read_complete = true; } } void calculate_real_power_send_sensiml(int16_t no_of_sample){ cy_rslt_t adc_result; int32_t ins_voltage = 0; int32_t ins_current = 0; int32_t actual_ins_voltage = 0; int32_t actual_ins_current = 0; int32_t ins_power = 0; int16_t real_power = 0; for(int16_t i=0; i<no_of_sample; i++) { adc_result = cyhal_adc_read_async_uv(&adc_obj, NUM_SCAN, result_arr); if(adc_result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } ins_voltage = result_arr[CHANNEL_0]/1000; ins_current = result_arr[CHANNEL_1]/1000; //printf("Voltage channel input: %4ldmV \t Current channel input: %4ldmV\r\n", (long int)ins_voltage, (long int)ins_current); actual_ins_voltage = (ins_voltage * 4)/1000; //multiplied by scale factor of sensor actual_ins_current = (ins_current * 1)/1000; //printf("ins voltage: %4ldmV \t ins current: %4ldmV\r\n", (long int)actual_ins_voltage, (long int)actual_ins_current); ins_power = ins_power + actual_ins_voltage * actual_ins_current; async_read_complete = false; //cyhal_system_delay_ms(1); cyhal_system_delay_us(7); //this delay is required to work adc read properly } real_power = ins_power/no_of_sample; send_sensor_data((int16_t)actual_ins_voltage, (int16_t)actual_ins_current, (int16_t)real_power); ins_power = 0; //int16_t power = real_power; //printf("Power consumption: %4ldW\r\n", (long int)real_power); } uint8_t input_string[15]; bool string_complete = false; size_t input_string_length = 15; void uart_receive_data(void){ while((int)cyhal_uart_readable(&cy_retarget_io_uart_obj)>0){ cyhal_uart_getc(&cy_retarget_io_uart_obj, &read_data, 0); append(input_string, read_data); } string_complete = true; } bool check_received_string(void){ bool receive_connect = false; if(string_complete){ if(!strcmp((void*)input_string,"connect")){ receive_connect = true; } for(int i=0; i<15; i++){ input_string[i] = '\0'; //clear the input string } string_complete = false; } return receive_connect; } bool check_received_string_disconnect(void){ bool receive_connect = false; if(string_complete){ if(!strcmp((void*)input_string,"disconnect")){ receive_connect = true; } for(int i=0; i<15; i++){ input_string[i] = '\0'; //clear the input string } string_complete = false; } return receive_connect; } void send_config_message(void){ uint8_t config_buf[CONFIG_BUF_SIZE] = "{\"sample_rate\":333, \"version\":1, \"samples_per_packet\":3, \"column_location\":{\"CurrentVoltageVoltage\":0, \"CurrentVoltageCurrent\":1, \"CurrentVoltagePower\":2}}\n"; size_t config_length = CONFIG_BUF_SIZE; cyhal_uart_write(&cy_retarget_io_uart_obj, (void*)config_buf, &config_length); } void send_sensor_data(int16_t voltage, int16_t current, int16_t power){ printf("%d, %d, %d\n", voltage, current, power); //cyhal_system_delay_ms(1); } void sensiml_connect_string(){ printf("{\"sample_rate\":333, " "\"version\":1, " "\"samples_per_packet\":3, " "\"column_location\":{" "\"SensorVoltage\":0, " "\"SensorCurrent\":1, " "\"SensorPower\":2" "}" "}\n"); }
The code is also attached.
The Device Plugin
The device plugin is attached. Device Plugins are a list of properties that describe how the DCL will collect data from your device. For example, the device plugin may contain a list of sample rates that your device supports. This allows the DCL to collect data from any device that has been built to accept the supported parameters below.
How to Import Device Plugin
Before importing you need to download and install SensiML Data Capture Lab from (https://sensiml.com/download/ ). The Data Capture Lab allows you to import Device Plugins via .SSF files. The .SSF file is attached below. For now, just note that you can import your SSF file through the menu item Edit → Import Device Plugin…
Next, you will be able to select the data communication protocol from the option. Simple Streaming Interface in our case.
In the next blog, we will discuss how we can record and preprocess the data using PSoC Kit and SensiML Data Capture Lab.