Grove - Wio-E5 Helium y Demo de Aplicación TinyML
Primeros Pasos
Aquí, vamos a construir un proyecto que utiliza tinyML y la red IoT LoRa de helium para prevenir la tala ilegal de madera.
Diagrama de Flujo
Hardware Requerido
Seeeduino XIAO nRF52840 Sense | Grove - Wio-E5 | Placa de Expansión Seeeduino XIAO |
---|---|---|
Herramientas de Software/Servicios Requeridos
- 1x Ardunino IDE
- 1x Helium IoT Console .
- 1 x Computadora
- 1 x cable USB Type-C
Configurar XIAO nRF52840 Sense en Arduino IDE
Algunos cables USB solo pueden suministrar energía y no pueden transferir datos. Si no tienes un cable USB o no sabes si tu cable USB puede transmitir datos, puedes verificar Seeed USB Type-C support USB 3.1.
Conecta el Seeed Studio XIAO nRF52840 (Sense) a tu computadora mediante un cable USB Type-C.
Configuración del software
- Paso 1. Descarga e instala la última versión de Arduino IDE según tu sistema operativo
-
Paso 2. Inicia la aplicación Arduino
-
Paso 3. Añade el paquete de placa Seeed Studio XIAO nRF52840 (Sense) a tu Arduino IDE
Navega a File > Preferences, y completa "Additional Boards Manager URLs" con la url de abajo: https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
Navega a Tools > Board > Boards Manager..., escribe la palabra clave "seeed nrf52" en el cuadro de búsqueda, selecciona la última versión de la placa que deseas, e instálala. Puedes instalar ambas.
- Paso 4. Selecciona tu placa y puerto
Placa
Después de instalar el paquete de placa, navega a Tools > Board y elige la placa que deseas, continúa para seleccionar "Seeed XIAO BLE Sense nRF52840" de "Seeed nRF mbed-enabled Boards". Ahora hemos terminado de configurar el Seeed Studio XIAO nRF52840 (Sense) para Arduino IDE.
Puerto
Navega a Tools > Port y selecciona el nombre del puerto serie del Seeed Studio XIAO nRF52840 (Sense) conectado. Es probable que sea COM3 o superior (COM1 y COM2 generalmente están reservados para puertos serie de hardware). El puerto serie del Seeed Studio XIAO nRF52840 (Sense) conectado generalmente contiene paréntesis que están escritos Seeed Studio XIAO nRF52840 para Seeed Studio XIAO nRF52840 o Seeed Studio XIAO nRF52840 Sense para Seeed Studio XIAO nRF52840 Sense.
- Paso 5. Navega a File > Examples > 01.Basics > Blink para abrir el ejemplo Blink
- Paso 6. Haz clic en el botón Upload para cargar el código de ejemplo Blink a la placa
Una vez cargado, verás el LED rojo integrado parpadeando con un retraso de 1 segundo entre cada parpadeo. Esto significa que la conexión es exitosa y ahora puedes explorar más proyectos con el Seeed Studio XIAO nRF52840 (Sense)!
Configuración de la Consola Helium LoRa WAN
Asegúrate de estar dentro de la cobertura de la red helium. Puedes encontrar la cobertura de la red en explorer.helium.com
Por favor asegúrate de la consistencia de la banda de frecuencia entre los nodos finales, gateway y la configuración de Helium que estás usando siguiendo esta instrucción. El plan de frecuencia que esta demostración aplica es para IN865.
La Red IoT de Helium usa el protocolo LoRaWAN para proporcionar conectividad a internet a dispositivos de "Internet de las Cosas" y es la sub red original en el ecosistema Helium. Desarrolladores y empresas alrededor del mundo dependen de la Red IoT de Helium para conectividad.
Preparación de la Consola IoT de Helium
- Paso 1. Visita el sitio web de Helium Console y regístrate para una nueva cuenta
- Paso 2. Después de iniciar sesión, haz clic en "Devices" para gestionar dispositivos.
- Paso 3. Luego haz clic en "Add New Device"
- Paso 4. Aquí, 1) Añade un nuevo dispositivo , 2) Copia el Dev EUI , 3) Copia el App EUI, 4) Copia el App Key, 5) Finalmente Haz clic en Save.
- Paso 5. Ahora puedes ver el dispositivo bajo la sección "Devices".
Ahora, la configuración de la consola helium está completa. podemos pasar a la parte de Software y subir datos a la consola helium a través de la red LoRa helium.
Preparación del Hardware
Coloca el XIAO nRF52840 BLE Sense en la placa de expansión Seeeduino XIAO y luego Conecta el Grove - Wio E5 al puerto UART de la placa de expansión XIAO.
Preparación del Software
Desarrollamos el modelo tinyML usando la herramienta Edge impulse, y puedes encontrar el conjunto de datos y detalles del proyecto aquí, también puedes clonar el proyecto y hacer las modificaciones según tu necesidad.
Por ahora, descarga la biblioteca tinyML de Edge Impulse desde aquí y añádela al arduino. Sigue esta guía para aprender cómo añadir bibliotecas ZIP.
Después de añadir la biblioteca, copia el código de abajo y pégalo en tu sketch de arduino y reemplaza las Credenciales de Helium LoRa WAN.
/* Edge Impulse ingestion SDK
* Copyright (c) 2022 EdgeImpulse Inc.
*
* 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.
*
*/
// If your target is limited in memory remove this macro to save 10K RAM
#define EIDSP_QUANTIZE_FILTERBANK 0
/*
** NOTE: If you run into TFLite arena allocation issue.
**
** This may be due to may dynamic memory fragmentation.
** Try defining "-DEI_CLASSIFIER_ALLOCATION_STATIC" in boards.local.txt (create
** if it doesn't exist) and copy this file to
** `<ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/`.
**
** See
** (https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the-installed-cores-located-)
** to find where Arduino installs cores on your machine.
**
** If the problem persists then there's not enough memory for this model and application.
*/
/*
** Developed by Salman Faris
** Date: 20/09/2023
*/
/* Includes ---------------------------------------------------------------- */
#include <PDM.h>
#include <Illegal_Logging_Detection_-_Vehicle_sound_-_XIAO-nRF52_inferencing.h>
#include <Arduino.h>
#include <U8x8lib.h>
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/*reset=*/U8X8_PIN_NONE);
/** Audio buffers, pointers and selectors */
typedef struct {
int16_t *buffer;
uint8_t buf_ready;
uint32_t buf_count;
uint32_t n_samples;
} inference_t;
static inference_t inference;
static signed short sampleBuffer[2048];
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
//LoRa Buffer
static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;
static int led = 0;
//Inference Data
int pred_index = 0; // Initialize pred_index
float pred_value = 0; // Initialize pred_value
static int at_send_check_response(char *p_ack, int timeout_ms, char *p_cmd, ...) {
int ch;
int num = 0;
int index = 0;
int startMillis = 0;
va_list args;
char cmd_buffer[256]; // Adjust the buffer size as needed
memset(recv_buf, 0, sizeof(recv_buf));
va_start(args, p_cmd);
vsprintf(cmd_buffer, p_cmd, args); // Format the command string
Serial1.print(cmd_buffer);
Serial.print(cmd_buffer);
va_end(args);
delay(200);
startMillis = millis();
if (p_ack == NULL) {
return 0;
}
do {
while (Serial1.available() > 0) {
ch = Serial1.read();
recv_buf[index++] = ch;
Serial.print((char)ch);
delay(2);
}
if (strstr(recv_buf, p_ack) != NULL) {
return 1;
}
} while (millis() - startMillis < timeout_ms);
return 0;
}
static void recv_prase(char *p_msg) {
if (p_msg == NULL) {
return;
}
char *p_start = NULL;
int data = 0;
int rssi = 0;
int snr = 0;
p_start = strstr(p_msg, "RX");
if (p_start && (1 == sscanf(p_start, "RX: \"%d\"\r\n", &data))) {
Serial.println(data);
u8x8.setCursor(2, 4);
u8x8.print("led :");
led = !!data;
u8x8.print(led);
if (led) {
digitalWrite(LED_BUILTIN, LOW);
} else {
digitalWrite(LED_BUILTIN, HIGH);
}
}
p_start = strstr(p_msg, "RSSI");
if (p_start && (1 == sscanf(p_start, "RSSI %d,", &rssi))) {
u8x8.setCursor(0, 6);
u8x8.print(" ");
u8x8.setCursor(2, 6);
u8x8.print("rssi:");
u8x8.print(rssi);
}
p_start = strstr(p_msg, "SNR");
if (p_start && (1 == sscanf(p_start, "SNR %d", &snr))) {
u8x8.setCursor(0, 7);
u8x8.print(" ");
u8x8.setCursor(2, 7);
u8x8.print("snr :");
u8x8.print(snr);
}
}
/**
* @brief Arduino setup function
*/
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial1.begin(9600);
// comment out the below line to cancel the wait for USB connection (needed for native USB)
while (!Serial)
;
Serial.println("Edge Impulse Inferencing Demo");
// summary of inferencing settings (from model_metadata.h)
ei_printf("Inferencing settings:\n");
ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
ei_printf("ERR: Could not allocate audio buffer (size %d), this could be due to the window length of your model\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
return;
}
u8x8.begin();
u8x8.setFlipMode(1);
u8x8.setFont(u8x8_font_chroma48medium8_r);
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Serial1.begin(9600);
Serial.print("E5 LORAWAN TEST\r\n");
u8x8.setCursor(0, 0);
if (at_send_check_response("+AT: OK", 100, "AT\r\n")) {
is_exist = true;
at_send_check_response("+ID: DevEui", 1000, "AT+ID=DevEui,\"XXXXXXXXXX"\r\n");
at_send_check_response("+ID: AppEui", 1000, "AT+ID=AppEui,\"XXXXXXXXXXX\"\r\n");
at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"XXXXXXXXXXXX\"\r\n");
at_send_check_response("+ID: DevAddr", 1000, "AT+ID=DevAddr\r\n");
at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
at_send_check_response("+DR: IN865", 1000, "AT+DR=IN865\r\n");
at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,0-2\r\n");
at_send_check_response("+CLASS: A", 1000, "AT+CLASS=A\r\n");
at_send_check_response("+PORT: 8", 1000, "AT+PORT=8\r\n");
delay(200);
u8x8.setCursor(5, 0);
u8x8.print("LoRaWAN");
is_join = true;
} else {
is_exist = false;
Serial.print("No E5 module found.\r\n");
u8x8.setCursor(0, 1);
u8x8.print("unfound E5 !");
}
//dht.begin();
}
/**
* @brief Arduino main function. Runs the inferencing loop.
*/
void loop() {
ei_printf("Starting inferencing in 2 seconds...\n");
delay(2000);
ei_printf("Recording...\n");
bool m = microphone_inference_record();
if (!m) {
ei_printf("ERR: Failed to record audio...\n");
return;
}
ei_printf("Recording done\n");
signal_t signal;
signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
signal.get_data = µphone_audio_signal_get_data;
ei_impulse_result_t result = { 0 };
EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
if (r != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", r);
return;
}
int pred_index = 0; // Initialize pred_index
float pred_value = 0; // Initialize pred_value
// 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: %.5f\n", result.classification[ix].label, result.classification[ix].value);
ei_printf("\n");
if (result.classification[ix].value > pred_value) {
pred_index = ix;
pred_value = result.classification[ix].value;
}
}
// Display inference result and Send message to Helium Console.
if ((pred_index == 1 && (pred_value > 0.8))) {
ei_printf("Vehicle Sound is Detected ");
ei_printf("\n");
int SOS = 10;
int DeviceID = 1;
if (is_exist) {
int ret = 0;
if (is_join) {
ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
if (ret) {
is_join = false;
} else {
at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
Serial.print("JOIN failed!\r\n\r\n");
delay(5000);
}
} else {
char cmd[128];
sprintf(cmd, "AT+CMSGHEX=\"%04X%04X\"\r\n", (int)DeviceID, (int)SOS);
ret = at_send_check_response("Done", 5000, cmd);
u8x8.setCursor(0, 2);
u8x8.print(" ");
u8x8.setCursor(2, 2);
u8x8.print("Vehicle Detected");
u8x8.setCursor(2, 3);
u8x8.print("Sending SOS");
if (ret) {
recv_prase(recv_buf);
} else {
Serial.print("Send failed!\r\n\r\n");
}
delay(5000);
}
} else {
delay(1000);
}
}
else {
u8x8.setCursor(0, 2);
u8x8.print(" ");
u8x8.setCursor(2, 2);
u8x8.print("Normal Condition");
u8x8.setCursor(2, 3);
u8x8.print("idle");
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif
}
/**
* @brief PDM buffer full callback
* Get data and call audio thread callback
*/
static void pdm_data_ready_inference_callback(void) {
int bytesAvailable = PDM.available();
// read into the sample buffer
int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);
if (inference.buf_ready == 0) {
for (int i = 0; i < bytesRead >> 1; i++) {
inference.buffer[inference.buf_count++] = sampleBuffer[i];
if (inference.buf_count >= inference.n_samples) {
inference.buf_count = 0;
inference.buf_ready = 1;
break;
}
}
}
}
/**
* @brief Init inferencing struct and setup/start PDM
*
* @param[in] n_samples The n samples
*
* @return { description_of_the_return_value }
*/
static bool microphone_inference_start(uint32_t n_samples) {
inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
if (inference.buffer == NULL) {
return false;
}
inference.buf_count = 0;
inference.n_samples = n_samples;
inference.buf_ready = 0;
// configure the data receive callback
PDM.onReceive(&pdm_data_ready_inference_callback);
PDM.setBufferSize(4096);
// initialize PDM with:
// - one channel (mono mode)
// - a 16 kHz sample rate
if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
ei_printf("Failed to start PDM!");
microphone_inference_end();
return false;
}
// set the gain, defaults to 20
PDM.setGain(127);
return true;
}
/**
* @brief Wait on new data
*
* @return True when finished
*/
static bool microphone_inference_record(void) {
inference.buf_ready = 0;
inference.buf_count = 0;
while (inference.buf_ready == 0) {
delay(10);
}
return true;
}
/**
* Get raw audio signal data
*/
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr) {
numpy::int16_to_float(&inference.buffer[offset], out_ptr, length);
return 0;
}
/**h
* @brief Stop PDM and release buffers
*/
static void microphone_inference_end(void) {
PDM.end();
free(inference.buffer);
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif
Reemplaza el DevEUI , AppEUI y APPKEY con tus credenciales obtenidas de la consola de Helium. También asegúrate de reemplazar el IN865 según el plan de FREQ de tu región.
at_send_check_response("+ID: DevEui", 1000, "AT+ID=DevEui,\"xxxxxxxxxxx\"\r\n");
at_send_check_response("+ID: AppEui", 1000, "AT+ID=AppEui,\"xxxxxxxxxxx\"\r\n");
at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"xxxxxxxxxxxxxxx\"\r\n");
at_send_check_response("+ID: DevAddr", 1000, "AT+ID=DevAddr\r\n");
at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
at_send_check_response("+DR: IN865", 1000, "AT+DR=IN865\r\n");
at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,0-2\r\n");
at_send_check_response("+CLASS: A", 1000, "AT+CLASS=A\r\n");
at_send_check_response("+PORT: 8", 1000, "AT+PORT=8\r\n");
En la sección siguiente, puedes ver que el XIAO verificará si hay algún Sonido de vehículo detectado y si se detecta, establecerá una conexión LoRa WAN a la consola de helium y enviará el dato 1 como símbolo de Sonido de vehículo detectado.
// Display inference result and Send message to Helium Console.
if ((pred_index == 1 && (pred_value > 0.8))) {
ei_printf("Vehicle Sound is Detected ");
ei_printf("\n");
int SOS = 10;
int DeviceID = 1;
if (is_exist) {
int ret = 0;
if (is_join) {
ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
if (ret) {
is_join = false;
} else {
at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
Serial.print("JOIN failed!\r\n\r\n");
delay(5000);
}
} else {
char cmd[128];
sprintf(cmd, "AT+CMSGHEX=\"%04X%04X\"\r\n", (int)DeviceID, (int)SOS);
ret = at_send_check_response("Done", 5000, cmd);
u8x8.setCursor(0, 2);
u8x8.print(" ");
u8x8.setCursor(2, 2);
u8x8.print("Vehicle Detected");
u8x8.setCursor(2, 3);
u8x8.print("Sending SOS");
if (ret) {
recv_prase(recv_buf);
} else {
Serial.print("Send failed!\r\n\r\n");
}
delay(5000);
}
} else {
delay(1000);
}
}
else {
u8x8.setCursor(0, 2);
u8x8.print(" ");
u8x8.setCursor(2, 2);
u8x8.print("Normal Condition");
u8x8.setCursor(2, 3);
u8x8.print("idle");
}
Demo
Asegúrate de estar dentro de la cobertura de la red helium. Puedes encontrar la cobertura de la red en explorer.helium.com
Después de cargar el código, el XIAO nRF52840 Sense capturará sonido y verificará si hay algún sonido de motor usando tinyML, así que intenta reproducir sonido de motor para activar la acción. Una vez que detecte el sonido del motor (1), XIAO establecerá la conexión de red Helium LoRa y enviará el comando "1" como símbolo de Sonido de Vehículo Detectado (2).
Puedes ver los datos en la consola de helium y su ventana de depuración.
Tareas Pendientes
Hasta ahora, hemos hecho la integración con tinyML y LoRa. y tenemos algunas tareas pendientes en las que puedes trabajar como ejercicio para mejorar el proyecto.
- Integrar la consola de helium con el panel de control y visualizar los datos
- Crear Alerta por Email/SMS/Teléfono con panel de control personalizado para informar al guardabosques.
Recursos
Hoja de datos:
Certificaciones:
SDK Relevante:
Soporte Técnico y Discusión de Productos
¡Gracias por elegir nuestros productos! Estamos aquí para brindarte diferentes tipos de soporte para asegurar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para satisfacer diferentes preferencias y necesidades.