Windows are essential for good natural ventilation. Our project aims to monitor natural ventilation habits in classrooms. And this week we implemented an anomaly detector for sliding windows. This will allow our window sensors to detect if the windows need maintenance due to difficulty in opening or closing. We have designed and trained a Machine Learning model to detect anomalies in the opening and closing of sliding windows through the IMU incorporated in the Arduino Nano 33 IoTArduino Nano 33 IoT and using the services of the Edge Impulse platform.
Tracking System for Classroom Ventilation Routines
A STEM project for classrooms
the VenTTracker project - VenTTracker #12 - Window Anomaly Detection. Edge Impulse & Arduino Nano 33 IoT
The problem
Our problem is to detect anomalous movements in the window due to elements that interfere with the movement of the window when opening or closing.
The solution
Our solution will be an intelligent device with embedded Machine Learning processing the data that the accelerometer of the Arduino Nano 33 IoT gives us and converting it into information that allows us to classify the movement as an anomaly or as normal movement.
Edge Impulse
To develop our model we have used the services of the Edge Impulse platform. Edge Impulse is the leading development platform for machine learning on edge devices, free for developers and trusted by companies around the world. For this, we are going to train a Machine Learning model capturing data in normal conditions of the window, in motion and at rest that we will label as Normal and then we will capture data in conditions of obstruction of the window and that we will label as Anomaly.
Data acquisition
Edge Impulse provides specific data capture firmware for many boards on the market. At the moment the Arduino Nano 33 IoT board is not fully supported but there are many other methods of capturing the data and uploading it to the platform. In our case, we have opted for the simplest, the Data Forwarder, which allows us to capture data from any device with a few lines of code.
#include <Arduino_LSM6DS3.h> #define CONVERT_G_TO_MS2 9.80665f #define FREQUENCY_HZ 50 #define INTERVAL_MS (1000 / (FREQUENCY_HZ + 1)) void setup() { Serial.begin(115200); Serial.println("Started"); if (!IMU.begin()) { Serial.println("Failed to initialize IMU!"); while (1); } } void loop() { static unsigned long last_interval_ms = 0; float x, y, z; if (millis() > last_interval_ms + INTERVAL_MS) { last_interval_ms = millis(); IMU.readAcceleration(x, y, z); Serial.print(x * CONVERT_G_TO_MS2); Serial.print('\t'); Serial.print(y * CONVERT_G_TO_MS2); Serial.print('\t'); Serial.println(z * CONVERT_G_TO_MS2); } }
In a command window:
C:\edge-impulse-data-forwarder Edge Impulse data forwarder v1.13.4 Endpoints: Websocket: wss://remote-mgmt.edgeimpulse.com API: https://studio.edgeimpulse.com/v1 Ingestion: https://ingestion.edgeimpulse.com ? Which device do you want to connect to? COM17 (Arduino AG (www.arduino.cc)) [SER] Connecting to COM17 [SER] Serial is connected (82:65:39:24:50:57:37:38:35:2E:31:20:FF:12:06:36) [WS ] Connecting to wss://remote-mgmt.edgeimpulse.com [WS ] Connected to wss://remote-mgmt.edgeimpulse.com [SER] Detecting data frequency... [SER] Detected data frequency: 51Hz [WS ] Device "CL01-W01" is now connected to project "javagoza-project-1" [WS ] Go to https://studio.edgeimpulse.com/studio/175/acquisition/training to build your machine learning model!
Impulse Creation
An impulse takes raw data, uses signal processing to extract features, and then uses a learning block to classify new data.
Nearest Neighbour Classifier
We tested live classification and surprise all the tests give the correct classification of the anomalies.
Model Testing
The model behaves very well but we observed that the model crashes when hitting the window hitting the window frame when closing. We can avoid this with a mechanical window, preventing certain values or adding more complexity to our model.
Deployment
Compiling the code and resolving conflicts
Reference: https://docs.edgeimpulse.com/docs/running-your-impulse-arduino
Static buffer inference
The first thing we are going to do is test the inference engine under controlled conditions without taking data from the accelerometer. We will treat a static buffer
We have installed the project as a library. And they provide us with several examples with the application skeleton ready to use. We start by testing the static buffer example to see that it compiles well.
The first time we compile we get two errors:
\src/edge-impulse-sdk/CMSIS/DSP/Include/arm_math.h:8742:3: error: '__STATIC_FORCEINLINE' does not name a type; did you mean '__STATIC_INLINE'? __STATIC_FORCEINLINE q7_t arm_bilinear_interp_q7( ^~~~~~~~~~~~~~~~~~~~ __STATIC_INLINE
This a problem with old versions of CMSIS Core (like the SAMD21 targets)
and
C:\Users\ealbertos\Documents\Arduino\ei_javagoza_static_buffer\ei_javagoza_static_buffer.ino:126:5: error: 'va_start' was not declared in this scope va_start(args, format);
These two problems are well documented in Edge Impulse documentation. So follow that guide:
We have to add the cstdarg.h library from the the C standard library that allows functions to accept an indefinite number of arguments.
We add the reference to the library in the sketch
#include <cstdarg>
And the macro define
#define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline #define __SSAT(ARG1, ARG2) \ __extension__ \ ({ \ int32_t __RES, __ARG1 = (ARG1); \ __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ __RES; \ })
The order in which we put the include and the define is important must be before the include to your library
#include <cstdarg> #define EIDSP_USE_CMSIS_DSP 1 #define EIDSP_LOAD_CMSIS_DSP_SOURCES 1 #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline #define __SSAT(ARG1, ARG2) \ __extension__ \ ({ \ int32_t __RES, __ARG1 = (ARG1); \ __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ __RES; \ }) #include <javagoza-project-1_inference.h>
We compiled again and everything works, it has been just 5 minutes following the Edge Impulse guide
Using library ei-javagoza-project-1-arduino-1.0.4 at version 1.0.4 in folder: C:\Users\ealbertos\Documents\Arduino\libraries\ei-javagoza-project-1-arduino-1.0.4 Sketch uses 14712 bytes (5%) of program storage space. Maximum is 262144 bytes. Global variables use 3708 bytes (11%) of dynamic memory, leaving 29060 bytes for local variables. Maximum is 32768 bytes.
Finally copy raw features here (for example from the 'Live classification' page)
// copy raw features here (for example from the 'Live classification' page) // see https://docs.edgeimpulse.com/docs/running-your-impulse-arduino static const float features[] = { -0.1500, -9.8600, -0.1600, -0.1400, -9.8500, -0.1600, -0.1500, -9.8600, -0.1700, -0.1500, -9.8400, -0.1600, -0.1500, -9.8600, -0.1700, -0.1400, -9.8600, -0.1700, -0.1500, -9.8600, -0.1700, -0.1400, -9.8700, -0.1700, -0.1400, -9.8500, -0.1700, -0.1500, -9.8600, -0.1600, -0.1400, -9.8700, -0.1700, -0.1400, -9.8600, -0.1700, -0.1400, -9.8500, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8600, -0.1700, -0.1400, -9.8500, -0.1700, -0.1500, -9.8400, -0.1700, -0.1600, -9.8600, -0.1700, -0.1500, -9.8600, -0.1800, -0.1500, -9.8600, -0.1700, -0.1500, -9.8500, -0.1700, -0.1400, -9.8600, -0.1700, -0.1600, -9.8400, -0.1700, -0.1500, -9.8600, -0.1700, -0.1400, -9.8600, -0.1600, -0.1400, -9.8600, -0.1700, -0.1400, -9.8600, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8600, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8500, -0.1700, -0.1400, -9.8600, -0.1700, -0.1500, -9.8800, -0.1700, -0.1500, -9.8700, -0.1700, -0.1500, -9.8600, -0.1700, -0.1400, -9.8500, -0.1700, -0.1400, -9.8500, -0.1700, -0.1500, -9.8400, -0.1700, -0.1500, -9.8700, -0.1700, -0.1400, -9.8600, -0.1700, -0.1500, -9.8600, -0.1600, -0.1500, -9.8400, -0.1800, -0.1400, -9.8600, -0.1700, -0.1600, -9.8500, -0.1600, -0.1500, -9.8600, -0.1700, -0.1400, -9.8600, -0.1700, -0.1500, -9.8700, -0.1600, -0.1500, -9.8500, -0.1700, -0.1500, -9.8700, -0.1600, -0.1500, -9.8500, -0.1700, -0.1600, -9.8500, -0.1600, -0.1400, -9.8500, -0.1700, -0.1400, -9.8600, -0.1700, -0.1400, -9.8500, -0.1700, -0.1500, -9.8500, -0.1800, -0.1400, -9.8600, -0.1700, -0.1600, -9.8400, -0.1600, -0.1400, -9.8600, -0.1700, -0.1500, -9.8600, -0.1700, -0.1400, -9.8500, -0.1800, -0.1500, -9.8500, -0.1700, -0.1600, -9.8500, -0.1600, -0.1400, -9.8600, -0.1700, -0.1400, -9.8600, -0.1800, -0.1400, -9.8500, -0.1700, -0.1500, -9.8700, -0.1700, -0.1500, -9.8600, -0.1700, -0.1500, -9.8700, -0.1600, -0.1500, -9.8600, -0.1700, -0.1500, -9.8600, -0.1700, -0.1500, -9.8700, -0.1600, -0.1400, -9.8600, -0.1700, -0.1600, -9.8500, -0.1600, -0.1500, -9.8400, -0.1700, -0.1400, -9.8700, -0.1600, -0.1500, -9.8500, -0.1700, -0.1400, -9.8600, -0.1700, -0.1400, -9.8500, -0.1600, -0.1400, -9.8400, -0.1500, -0.1500, -9.8400, -0.1800, -0.1400, -9.8700, -0.1700, -0.1400, -9.8500, -0.1800, -0.1600, -9.8600, -0.1700, -0.1400, -9.8600, -0.1600, -0.1500, -9.8600, -0.1700, -0.1500, -9.8700, -0.1700, -0.1500, -9.8600, -0.1800, -0.1400, -9.8500, -0.1700, -0.1500, -9.8700, -0.1600, -0.1500, -9.8500, -0.1600, -0.1500, -9.8500, -0.1700, -0.1600, -9.8500, -0.1700, -0.1500, -9.8600, -0.1600, -0.1500, -9.8600, -0.1600, -0.1500, -9.8500, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8500, -0.1700, -0.1500, -9.8600, -0.1700, -0.1300, -9.8600, -0.1700, -0.1600, -9.8400, -0.1600, -0.1500, -9.8500, -0.1700 };
This is the final sketch
/* Edge Impulse Arduino examples * Copyright (c) 2021 EdgeImpulse Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include <cstdarg> #define EIDSP_USE_CMSIS_DSP 1 #define EIDSP_LOAD_CMSIS_DSP_SOURCES 1 #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline #define __SSAT(ARG1, ARG2) \ __extension__ \ ({ \ int32_t __RES, __ARG1 = (ARG1); \ __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ __RES; \ }) /* Includes ---------------------------------------------------------------- */ #include <javagoza-project-1_inference.h> static const float features[] = { -0.2200, -9.8700, -0.1300, -0.2300, -9.8700, -0.1700, -0.2300, -9.8500, -0.1400, -0.2200, -9.8800, -0.1700, -0.2100, -9.8600, -0.1500, -0.2200, -9.8800, -0.1200, -0.2300, -9.8700, -0.1200, -0.2200, -9.8700, -0.1700, -0.2200, -9.8400, -0.1300, -0.2100, -9.8500, -0.1400, -0.2200, -9.8500, -0.1600, -0.2200, -9.8400, -0.1400, -0.2200, -9.8400, -0.1400, -0.2100, -9.8700, -0.1400, -0.2300, -9.8700, -0.1400, -0.2100, -9.8600, -0.1500, -0.2100, -9.8200, -0.1500, -0.2300, -9.8500, -0.1500, -0.2100, -9.8500, -0.1400, -0.2100, -9.8600, -0.1400, -0.2000, -9.8400, -0.1200, -0.2100, -9.8500, -0.1400, -0.2100, -9.8500, -0.1500, -0.2200, -9.8500, -0.1600, -0.2100, -9.8500, -0.1300, -0.2200, -9.8600, -0.1500, -0.2000, -9.8600, -0.1500, -0.2200, -9.8600, -0.1600, -0.2100, -9.8700, -0.1500, -0.2100, -9.8400, -0.1400, -0.2200, -9.8600, -0.1400, -0.2200, -9.8500, -0.1400, -0.2300, -9.8500, -0.1400, -0.2100, -9.8300, -0.1500, -0.2200, -9.8700, -0.1600, -0.2200, -9.8600, -0.1500, -0.2200, -9.8500, -0.1400, -0.2100, -9.8700, -0.1500, -0.2100, -9.8300, -0.1500, -0.2100, -9.8700, -0.1600, -0.2200, -9.8500, -0.1500, -0.2200, -9.8400, -0.1500, -0.2200, -9.8600, -0.1600, -0.2100, -9.8500, -0.1500, -0.2200, -9.8400, -0.1400, -0.2300, -9.8600, -0.1500, -0.2100, -9.8400, -0.1400, -0.2200, -9.8700, -0.1700, -0.2100, -9.8400, -0.1600, -0.2200, -9.8600, -0.1400, -0.2100, -9.8700, -0.1500, -0.2200, -9.8900, -0.1600, -0.2200, -9.8700, -0.1600, -0.2100, -9.8500, -0.1400, -0.2100, -9.8400, -0.1400, -0.2200, -9.8500, -0.1600, -0.2100, -9.8500, -0.1400, -0.2100, -9.8500, -0.1500, -0.2200, -9.8700, -0.1600, -0.2200, -9.8400, -0.1400, -0.2200, -9.8600, -0.1400, -0.2100, -9.8500, -0.1700, -0.2200, -9.8600, -0.1300, -0.2000, -9.8500, -0.1300, -0.2200, -9.8600, -0.1600, -0.2200, -9.8600, -0.1400, -0.2200, -9.8600, -0.1500, -0.2200, -9.8600, -0.1500, -0.2200, -9.8600, -0.1400, -0.2200, -9.8700, -0.1400, -0.2300, -9.8700, -0.1500, -0.2300, -9.8600, -0.1600, -0.2400, -9.8400, -0.1600, -0.2300, -9.8700, -0.1300, -0.2100, -9.8600, -0.1500, -0.2100, -9.8600, -0.1600, -0.2200, -9.8600, -0.1500, -0.2200, -9.8600, -0.1600, -0.2300, -9.8400, -0.1500, -0.2200, -9.8700, -0.1300, -0.2200, -9.8600, -0.1500, -0.2200, -9.8700, -0.1700, -0.2300, -9.8300, -0.1700, -0.2300, -9.8600, -0.1600, -0.2200, -9.8400, -0.1800, -0.2200, -9.8500, -0.1400, -0.2300, -9.8600, -0.1800, -0.2300, -9.8400, -0.1600, -0.2200, -9.8500, -0.1400, -0.2100, -9.8700, -0.1600, -0.2300, -9.8400, -0.1400, -0.2200, -9.8700, -0.1500, -0.2200, -9.8500, -0.1700, -0.2100, -9.8500, -0.1400, -0.2200, -9.8900, -0.1500, -0.2300, -9.8500, -0.1600, -0.2300, -9.8600, -0.1300, -0.2300, -9.8700, -0.1200, -0.2200, -9.8700, -0.1700, -0.2200, -9.8700, -0.1600, -0.2200, -9.8500, -0.1500, -0.2300, -9.8500, -0.1300 // copy raw features here (for example from the 'Live classification' page) // see https://docs.edgeimpulse.com/docs/running-your-impulse-arduino }; /** * @brief Copy raw feature data in out_ptr * Function called by inference library * * @param[in] offset The offset * @param[in] length The length * @param out_ptr The out pointer * * @return 0 */ int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) { memcpy(out_ptr, features + offset, length * sizeof(float)); return 0; } /** * @brief Arduino setup function */ void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Edge Impulse Inferencing Demo"); } /** * @brief Arduino main function */ void loop() { ei_printf("Edge Impulse standalone inferencing (Arduino)\n"); if (sizeof(features) / sizeof(float) != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) { ei_printf("The size of your 'features' array is not correct. Expected %lu items, but had %lu\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(features) / sizeof(float)); delay(1000); return; } ei_impulse_result_t result = { 0 }; // the features are stored into flash, and we don't want to load everything into RAM signal_t features_signal; features_signal.total_length = sizeof(features) / sizeof(features[0]); features_signal.get_data = &raw_feature_get_data; // invoke the impulse EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, false /* debug */); ei_printf("run_classifier returned: %d\n", res); if (res != 0) return; // print the predictions ei_printf("Predictions "); ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)", result.timing.dsp, result.timing.classification, result.timing.anomaly); ei_printf(": \n"); ei_printf("["); for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { ei_printf("%.5f", result.classification[ix].value); #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf(", "); #else if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) { ei_printf(", "); } #endif } #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf_float(result.anomaly); #endif ei_printf("]\n"); // human-readable predictions for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { ei_printf(" %s: ", result.classification[ix].label); ei_printf_float(result.classification[ix].value); ei_printf("\n"); } #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf(" anomaly score: "); ei_printf_float(result.anomaly); ei_printf("\n"); #endif delay(1000); } /** * @brief Printf function uses vsnprintf and output using Arduino Serial * * @param[in] format Variable argument list */ void ei_printf(const char *format, ...) { static char print_buf[1024] = { 0 }; va_list args; va_start(args, format); int r = vsnprintf(print_buf, sizeof(print_buf), format, args); va_end(args); if (r > 0) { Serial.write(print_buf); } } /** * @brief printf_float function uses Serial.print to format and print float with 5 decimals * * @param[in] f the float to print */ void ei_printf_float(float f) { Serial.print(f,5); }
And the results with the static buffer:
Inference with real-time IMU data
We add real-time data taken directly from the LSM6DS3 Arduino Nano 33 Iot IMU
/* Edge Impulse Arduino examples * Copyright (c) 2021 EdgeImpulse Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include <cstdarg> #define EIDSP_USE_CMSIS_DSP 1 #define EIDSP_LOAD_CMSIS_DSP_SOURCES 1 #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline #define __SSAT(ARG1, ARG2) \ __extension__ \ ({ \ int32_t __RES, __ARG1 = (ARG1); \ __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ __RES; \ }) /* Includes ---------------------------------------------------------------- */ #include <javagoza-project-1_inference.h> #include <Arduino_LSM6DS3.h> /* Constant defines -------------------------------------------------------- */ #define CONVERT_G_TO_MS2 9.80665f /* Private variables ------------------------------------------------------- */ static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal /** * @brief Arduino setup function */ void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Edge Impulse Inferencing Demo"); if (!IMU.begin()) { ei_printf("Failed to initialize IMU!\r\n"); } else { ei_printf("IMU initialized\r\n"); } if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) { ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 3 (the 3 sensor axes)\n"); return; } } /** * @brief Printf function uses vsnprintf and output using Arduino Serial * * @param[in] format Variable argument list */ void ei_printf(const char *format, ...) { static char print_buf[1024] = { 0 }; va_list args; va_start(args, format); int r = vsnprintf(print_buf, sizeof(print_buf), format, args); va_end(args); if (r > 0) { Serial.write(print_buf); } } /** * @brief Get data and run inferencing * * @param[in] debug Get debug info if true */ void loop() { ei_printf("\nStarting inferencing in 2 seconds...\n"); delay(2000); ei_printf("Sampling...\n"); // Allocate a buffer here for the values we'll read from the IMU float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 }; for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) { // Determine the next tick (and then sleep later) uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000); IMU.readAcceleration(buffer[ix], buffer[ix + 1], buffer[ix + 2]); buffer[ix + 0] *= CONVERT_G_TO_MS2; buffer[ix + 1] *= CONVERT_G_TO_MS2; buffer[ix + 2] *= CONVERT_G_TO_MS2; delayMicroseconds(next_tick - micros()); } // Turn the raw buffer in a signal which we can the classify signal_t signal; int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal); if (err != 0) { ei_printf("Failed to create signal from buffer (%d)\n", err); return; } // Run the classifier ei_impulse_result_t result = { 0 }; err = run_classifier(&signal, &result, debug_nn); if (err != EI_IMPULSE_OK) { ei_printf("ERR: Failed to run classifier (%d)\n", err); return; } // print the predictions ei_printf("Predictions "); ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)", result.timing.dsp, result.timing.classification, result.timing.anomaly); ei_printf(": \n"); for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { ei_printf(" %s: ", result.classification[ix].label); Serial.println(result.classification[ix].value, 5); } #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf(" anomaly score: " ); Serial.println(result.anomaly, 3); #endif }
Capture with several normal cases and one anomaly. The results are very good.
The detector in action
Normal condition, no anomalies.
Anomaly detected, send alert:
Code used in the demo video
Sketch uses 61152 bytes (23%) of program storage space. Maximum is 262144 bytes.
Global variables use 5660 bytes (17%) of dynamic memory, leaving 27108 bytes for local variables. Maximum is 32768 bytes.
/* * ei_nano_33_iot_accelerometer_demo * Modified by :Enrique Albertos * Date: 2021-04-11 * * Edge Impulse Arduino examples * Copyright (c) 2021 EdgeImpulse Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* needed for Arduino nano 33 IoT with Arduino IDE -------------------------- */ #include <cstdarg> #define EIDSP_USE_CMSIS_DSP 1 #define EIDSP_LOAD_CMSIS_DSP_SOURCES 1 #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline #define __SSAT(ARG1, ARG2) \ __extension__ \ ({ \ int32_t __RES, __ARG1 = (ARG1); \ __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ __RES; \ }) /* Includes ---------------------------------------------------------------- */ #include <javagoza-project-1_inference.h> #include <Arduino_LSM6DS3.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Wire.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels /* Constant defines -------------------------------------------------------- */ #define CONVERT_G_TO_MS2 9.80665f /* Private variables ------------------------------------------------------- */ static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal /* Logos & images -------------------------------------------------------- */ #define TITLE_HEIGHT 32 #define TITLE_WIDTH 112 // title logo 8 pixels per Byte Little Endian Horizontal static const unsigned char PROGMEM designChallengeTitle[] ={ 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x01, 0x80, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x67, 0x3C, 0x79, 0x1F, 0x3E, 0x0F, 0x3C, 0xD0, 0x38, 0x18, 0xCF, 0x11, 0x1C, 0x78, 0x63, 0x7E, 0xD9, 0x9F, 0x3E, 0x0F, 0x6E, 0xF0, 0x6C, 0x18, 0x1F, 0xBB, 0x36, 0x7C, 0x63, 0x66, 0xC9, 0xB3, 0x37, 0x06, 0x66, 0xE0, 0x0C, 0x18, 0x01, 0xBB, 0x72, 0xCC, 0x63, 0x7E, 0xE1, 0xB3, 0x37, 0x06, 0x66, 0xC0, 0x3C, 0x18, 0x07, 0xBB, 0x38, 0xFC, 0x63, 0x7E, 0x79, 0xB3, 0x37, 0x06, 0x66, 0xC0, 0x7C, 0x18, 0xDF, 0xBB, 0x1E, 0xFC, 0x67, 0x60, 0x1D, 0xB3, 0x37, 0x06, 0x66, 0xC0, 0xCC, 0x18, 0xD9, 0xBB, 0x06, 0xC0, 0x66, 0x66, 0xDD, 0x9F, 0x37, 0x06, 0x66, 0xC0, 0xEC, 0x0D, 0xD9, 0xBB, 0x76, 0xEC, 0x7C, 0x3C, 0xF9, 0x9F, 0x37, 0x06, 0x7C, 0xC0, 0x7C, 0x0F, 0x9F, 0x9F, 0x3E, 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x10, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x30, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xB0, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xBF, 0x1E, 0x66, 0x38, 0xB8, 0xF8, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3F, 0x3F, 0x66, 0x7D, 0xF8, 0xF9, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x33, 0x03, 0x66, 0xCC, 0xCD, 0x99, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x33, 0x1F, 0x66, 0xFC, 0xCD, 0x9B, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xB3, 0x3B, 0x66, 0xE1, 0xCD, 0x9B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xB3, 0x33, 0x66, 0xC4, 0xCD, 0x99, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xB3, 0x37, 0x66, 0xEC, 0xCC, 0xF9, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x33, 0x3F, 0x66, 0x7C, 0xCC, 0xF9, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #define EDGEIMPULSE_LOGOWIDTH 128 #define EDGEIMPULSE_LOGOHEIGHT 32 static const unsigned char PROGMEM edgeimpulsearduino [] = { 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x20, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, 0x70, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x3F, 0xFE, 0x70, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x7F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE1, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x07, 0xF3, 0xF8, 0x0F, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xF8, 0x01, 0xF3, 0xE0, 0x07, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0xFF, 0xC0, 0x03, 0xE0, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x7F, 0x81, 0x81, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xE0, 0x00, 0x3F, 0x01, 0x81, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0xC3, 0xF8, 0x3F, 0x07, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0xC3, 0xF8, 0x1E, 0x07, 0xF0, 0xF0, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x18, 0x00, 0x01, 0x00, 0x03, 0xC3, 0xF8, 0x3F, 0x07, 0xE0, 0xF0, 0x00, 0x3F, 0xFF, 0xFE, 0x1F, 0xFC, 0x00, 0x01, 0x00, 0x03, 0xE0, 0x00, 0x3F, 0x81, 0x81, 0xF0, 0x00, 0x7F, 0xFF, 0xFE, 0x3F, 0xFC, 0x00, 0x03, 0x80, 0x01, 0xE0, 0x00, 0x7F, 0x81, 0x81, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0x3F, 0xFE, 0x00, 0x7F, 0xFC, 0x01, 0xF0, 0x00, 0xFF, 0xC0, 0x03, 0xE0, 0x00, 0x7F, 0xFF, 0xFE, 0x3F, 0xFE, 0x00, 0x3F, 0xFC, 0x01, 0xF8, 0x01, 0xF3, 0xE0, 0x07, 0xE0, 0x00, 0x3F, 0xFF, 0xFE, 0x1F, 0xFE, 0x00, 0x01, 0x00, 0x00, 0xFC, 0x07, 0xE1, 0xF8, 0x0F, 0xC0, 0x00, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x73, 0xCF, 0x13, 0x7D, 0x93, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x73, 0xEF, 0xB3, 0x7D, 0x97, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x73, 0x6C, 0xF3, 0x31, 0xF6, 0x60, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFB, 0xEC, 0xF3, 0x31, 0xF6, 0x60, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFB, 0xEC, 0xF3, 0x31, 0xF6, 0x60, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0xDB, 0x6F, 0x9F, 0x7D, 0xB7, 0xC0, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9B, 0x6F, 0x0E, 0x7D, 0x33, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #define ALERT_WIDTH 40 #define ALERT_HEIGHT 32 static const unsigned char PROGMEM alert [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x01, 0x87, 0xFF, 0xE1, 0x80, 0x03, 0xDF, 0x81, 0xFB, 0xC0, 0x03, 0xBE, 0x00, 0x7D, 0xC0, 0x07, 0x78, 0x00, 0x1E, 0xE0, 0x0E, 0x70, 0x3C, 0x0E, 0x70, 0x0E, 0xE0, 0x3C, 0x07, 0x70, 0x0C, 0xE0, 0x3C, 0x07, 0x30, 0x1D, 0xC0, 0x3C, 0x03, 0xB8, 0x19, 0xC0, 0x3C, 0x03, 0x98, 0x19, 0x80, 0x3C, 0x01, 0x98, 0x1B, 0x80, 0x3C, 0x01, 0xD8, 0x1B, 0x80, 0x3C, 0x01, 0xD8, 0x1B, 0x80, 0x3C, 0x01, 0xD8, 0x1B, 0x80, 0x3C, 0x01, 0xD8, 0x19, 0x80, 0x18, 0x01, 0x98, 0x19, 0x80, 0x00, 0x01, 0x98, 0x1D, 0xC0, 0x3C, 0x03, 0xB8, 0x0D, 0xC0, 0x3C, 0x03, 0xB0, 0x0C, 0xE0, 0x3C, 0x07, 0x30, 0x0E, 0xF0, 0x3C, 0x0F, 0x70, 0x07, 0x78, 0x00, 0x1E, 0xE0, 0x07, 0x3C, 0x00, 0x3C, 0xE0, 0x03, 0x9F, 0x00, 0xF9, 0xC0, 0x01, 0x8F, 0xFF, 0xF1, 0x80, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* Display variables -------------------------------------------------------- */ // double buffer for the display GFXcanvas1 canvas(SCREEN_WIDTH, SCREEN_HEIGHT); // 128x32 pixel canvas // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); /** * @brief Arduino setup function */ void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Edge Impulse Inferencing Demo"); delay(1500); // wait for serial // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } blinkLogo(designChallengeTitle, TITLE_WIDTH, TITLE_HEIGHT, 1000); blinkLogo(edgeimpulsearduino, EDGEIMPULSE_LOGOWIDTH, EDGEIMPULSE_LOGOHEIGHT, 1000); if (!IMU.begin()) { ei_printf("Failed to initialize IMU!\r\n"); } else { ei_printf("IMU initialized\r\n"); } if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) { ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 3 (the 3 sensor axes)\n"); return; } } /** * @brief Printf function uses vsnprintf and output using Arduino Serial * * @param[in] format Variable argument list */ void ei_printf(const char *format, ...) { static char print_buf[1024] = { 0 }; va_list args; va_start(args, format); int r = vsnprintf(print_buf, sizeof(print_buf), format, args); va_end(args); if (r > 0) { Serial.write(print_buf); } } /** * @brief Get data and run inferencing * * @param[in] debug Get debug info if true */ void loop() { ei_printf("Sampling...\n"); // Allocate a buffer here for the values we'll read from the IMU float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 }; for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) { // Determine the next tick (and then sleep later) uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000); IMU.readAcceleration(buffer[ix], buffer[ix + 1], buffer[ix + 2]); buffer[ix + 0] *= CONVERT_G_TO_MS2; buffer[ix + 1] *= CONVERT_G_TO_MS2; buffer[ix + 2] *= CONVERT_G_TO_MS2; delayMicroseconds(next_tick - micros()); } // Turn the raw buffer in a signal which we can the classify signal_t signal; int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal); if (err != 0) { ei_printf("Failed to create signal from buffer (%d)\n", err); return; } // Run the classifier ei_impulse_result_t result = { 0 }; err = run_classifier(&signal, &result, debug_nn); if (err != EI_IMPULSE_OK) { ei_printf("ERR: Failed to run classifier (%d)\n", err); return; } // print the predictions ei_printf("Predictions "); ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)", result.timing.dsp, result.timing.classification, result.timing.anomaly); ei_printf(": \n"); for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { ei_printf(" %s: ", result.classification[ix].label); Serial.println(result.classification[ix].value, 5); } if(result.classification[1].value>.65f) { blinkLogo(alert, ALERT_WIDTH, ALERT_HEIGHT,500); } else { drawLogo(edgeimpulsearduino, EDGEIMPULSE_LOGOWIDTH, EDGEIMPULSE_LOGOHEIGHT); } #if EI_CLASSIFIER_HAS_ANOMALY == 1 ei_printf(" anomaly score: " ); Serial.println(result.anomaly, 3); #endif } /** * @brief Send a logo to the Display and center it */ void drawLogo(const unsigned char* logo, const int width, const int height ) { display.clearDisplay(); display.drawBitmap( (display.width() - width ) / 2, (display.height() - height) / 2, logo, width, height, 1); display.display(); } /** * @brief blinks a logo on the display */ void blinkLogo(const unsigned char* logo, const int width, const int height, const int blinkPeriod) { display.clearDisplay(); drawLogo(logo, width, height); //Invert and restore display, pausing in-between display.invertDisplay(true); delay(blinkPeriod); display.invertDisplay(false); delay(blinkPeriod); }
Connection to the Arduino IoT cloud
We connect our detector and sensor to the Arduino IoT cloud.
Add the includes as in VenTTracker #10 - Ventilation Monitor on Arduino IoT Cloud
#include "arduino_secrets.h" #include "thingProperties.h"
Add cloud setup. We use callbacks to allow the Wi-Fi connection to be established without problems without affecting the communication with the cryptoprocessor that will secure the connection.
initProperties(); // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection); ArduinoCloud.addCallback(ArduinoIoTCloudEvent::CONNECT, onIoTConnect); ArduinoCloud.addCallback(ArduinoIoTCloudEvent::DISCONNECT, onIoTDisconnect); ArduinoCloud.addCallback(ArduinoIoTCloudEvent::SYNC, onIoTSync); setDebugMessageLevel(2); delay(3000); ArduinoCloud.printDebugInfo();
and update cloud variables in loop
ArduinoCloud.update();
Sketch uses 178028 bytes (67%) of program storage space. Maximum is 262144 bytes.
Global variables use 23488 bytes (71%) of dynamic memory, leaving 9280 bytes for local variables. Maximum is 32768 bytes.
Conclusions
Two years ago we started to develop projects with TinyML and Tensorflow Lite like these two:
- Tap with voice control of the temperature: https://www.hackster.io/javagoza/voice-controlled-faucet-09e066
- Wake word detection with Sparkfun Artemis ATP https://www.hackster.io/javagoza/artemis-atp-wake-word-detection-d95f08
The most tedious part was the data capture and data processing for the modeling of our inference engine. The same type of work that then took us a week of work with the Edge Impulse platform has taken us even a couple of hours.
<< Previous VenTTracker Blog | Next VenTTracker Blog >> |
---|---|
VenTTracker #11 - Wireless Environmental Monitor on Arduino Nano 33 IoT | VenTTracker #14 (aka #11 bis) - Environmental Monitor Revisited - Version 2 |
Top Comments