Skip to main content

Desarrollar aplicación Edge Impulse a la nube vía Helium

Actualizable a Sensores Industriales

Con el controlador S2110 de SenseCAP y el registrador de datos S2100, puedes convertir fácilmente el Grove en un sensor LoRaWAN®. Seeed no solo te ayuda con el prototipado, sino que también te ofrece la posibilidad de expandir tu proyecto con la serie SenseCAP de sensores industriales robustos.

La carcasa IP66, configuración Bluetooth, compatibilidad con la red global LoRaWAN®, batería integrada de 19 Ah, y el potente soporte de la APP hacen del SenseCAP S210x la mejor opción para aplicaciones industriales. La serie incluye sensores para humedad del suelo, temperatura y humedad del aire, intensidad de luz, CO2, EC, y una estación meteorológica 8 en 1. Prueba el último SenseCAP S210x para tu próximo proyecto industrial exitoso.

Sensor Industrial SenseCAP
S2100
Registrador de Datos
S2101
Temp. y Humedad del Aire
S2102
Luz
S2103
Temp. y Humedad del Aire y CO2
S2104
Humedad y Temp. del Suelo
S2105
Humedad y Temp. del Suelo y EC
S2110
Controlador LoRaWAN®
S2120
Estación Meteorológica 8 en 1

Herramientas que Utilizamos

note

Antes de comenzar esta sección, asegúrate de conocer la producción de Wio Terminal Para más detalles, por favor lee

Este artículo muestra una solución para personas que quieren usar edgeimpulse para generar modelos y conectar con la nube. En nuestra demostración, usaremos google sheet. Es la forma directa y

Configuración de Helium

Paso 1. Crear una Integración con soporte para Google Form

Este paso es similar a los pasos en el artículo Integrate into Google Sheets via Helium

Lo que necesitamos hacer es nombrar la integración y simplemente guardar la configuración.

Conectando a Google Form

  • Crear

  • Conectar con Google Sheets

  • Enlazar con el ID de Google Form

Paso 2. Crear una Función con la api de Google Form y características de Decodificador

Asegúrate de que Google Form esté conectado con la Función, completado con el ID que obtuvimos de los pasos anteriores.

Necesitamos crear una Función con soporte de Decodificador para nuestro flujo de transferencia de datos, así.

function Decoder(bytes, port) {

var decoded = {};

function transformers(bytes) {
if (bytes[0] == 255 || bytes[0] == 0) {
value = bytes[2] * 256 + bytes[3];
}
return value;
}

if (port == 8) {
decoded.class = transformers(bytes.slice(0, 4));
}

var decodedPayload = {
"class": decoded.class
};

// END TODO

return Serialize(decodedPayload)
}

var field_mapping = {
"class": "entry.39410305"
};

function Serialize(payload) {
var str = [];
for (var key in payload) {
if (payload.hasOwnProperty(key)) {
var name = encodeURIComponent(field_mapping[key]);
var value = encodeURIComponent(payload[key]);
str.push(name + "=" + value);
}
}
return str.join("&");
}
// DO NOT REMOVE: Google Form Function\

Paso 3. Configurar los Flujos

Asegurar la conexión

Configuración de Edge Impulse

Configuración de Arduino (Wio Terminal)

note

Debido a que los sensores y entornos son diferentes, la grabación directa de modelos entrenados en diferentes placas portadoras no siempre es ideal. Los modelos confiables necesitan ser entrenados por los propios usuarios, por lo que solo se proporciona código de prueba para una experiencia rápida.

Tener una experiencia

Después de que generamos la biblioteca desde Edge Impulse. Necesitamos modificar el código para el envío de datos vía LoRa en Wio Terminal. Si solo quieres tener una experiencia, simplemente copia el código de abajo y grábalo en tu Wio Terminal vía Arduino IDE.

Graba el código de prueba de abajo.

#include <AIot_Example_inferencing.h>
#include"LIS3DHTR.h"
#include"TFT_eSPI.h"
LIS3DHTR<TwoWire> lis;
TFT_eSPI tft;
#include <SoftwareSerial.h>
#include <Arduino.h>
#include <SensirionI2CSht4x.h>
#include <Wire.h>

SoftwareSerial mySerial(A0, A1); // RX, TX

SensirionI2CSht4x sht4x;

static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;

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;
memset(recv_buf, 0, sizeof(recv_buf));
va_start(args, p_cmd);
mySerial.printf(p_cmd, args);
Serial.printf(p_cmd, args);
va_end(args);
delay(200);
startMillis = millis();

if (p_ack == NULL)
{
return 0;
}

do
{
while (mySerial.available() > 0)
{
ch = mySerial.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);
}

p_start = strstr(p_msg, "RSSI");
if (p_start && (1 == sscanf(p_start, "RSSI %d,", &rssi)))
{
Serial.println(rssi);
}

p_start = strstr(p_msg, "SNR");
if (p_start && (1 == sscanf(p_start, "SNR %d", &snr)))
{
Serial.println(snr);
}
}
////// Send message block end


/* Constant defines -------------------------------------------------------- */
#define CONVERT_G_TO_MS2 9.80665f
#define MAX_ACCEPTED_RANGE 2.0f // starting 03/2022, models are generated setting range to +-2, but this example use Arudino library which set range to +-4g. If you are using an older model, ignore this value and use 4.0f instead

/* 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");

tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_WHITE);

lis.begin(Wire1);

if (!lis.available()) {
Serial.println("Failed to initialize IMU!");
while (1);
}
else {
ei_printf("IMU initialized\r\n");
}
lis.setOutputDataRate(LIS3DHTR_DATARATE_100HZ); // Setting output data rage to 25Hz, can be set up tp 5kHz
lis.setFullScaleRange(LIS3DHTR_RANGE_16G); // Setting scale range to 2g, select from 2,4,8,16g


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;
}

mySerial.begin(9600);

Wire.begin();

uint16_t error;
char errorMessage[256];

sht4x.begin(Wire);

uint32_t serialNumber;
error = sht4x.serialNumber(serialNumber);
delay(5000);
if (error) {
Serial.print("Error trying to execute serialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("Serial Number: ");
Serial.println(serialNumber);
}

Serial.print("E5 LORAWAN TEST\r\n");

if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
{
is_exist = true;
at_send_check_response("+ID: DevEui", 1000, "AT+ID=DevEui,\"608XXXXXXXXEE7\"\r\n");
at_send_check_response("+ID: AppEui", 1000, "AT+ID=AppEui,\"608XXXXXXXX85D\"\r\n");
at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
at_send_check_response("+DR: EU868", 1000, "AT+DR=EU868\r\n");
at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,0-2\r\n");
at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"E1EF1AC8XXXXXXXXXXXXXXXX05C5\"\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);
is_join = true;
}
else
{
is_exist = false;
Serial.print("No E5 module found.\r\n");
}
}

/**
* @brief Return the sign of the number
*
* @param number
* @return int 1 if positive (or 0) -1 if negative
*/
float ei_get_sign(float number) {
return (number >= 0.0) ? 1.0 : -1.0;
}

/**
* @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);
lis.getAcceleration(&buffer[ix], &buffer[ix + 1], &buffer[ix + 2]);

for (int i = 0; i < 3; i++) {
if (fabs(buffer[ix + i]) > MAX_ACCEPTED_RANGE) {
buffer[ix + i] = ei_get_sign(buffer[ix + i]) * MAX_ACCEPTED_RANGE;
}
}

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: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif

int classification_flag = 0;

if (result.classification[1].value > 0.7) {
tft.fillScreen(TFT_PURPLE);
tft.setFreeFont(&FreeSansBoldOblique12pt7b);
tft.drawString("Wave", 20, 80);
delay(1000);
tft.fillScreen(TFT_WHITE);
classification_flag = 1;
}

if (result.classification[2].value > 0.7) {
tft.fillScreen(TFT_RED);
tft.setFreeFont(&FreeSansBoldOblique12pt7b);
tft.drawString("Circle", 20, 80);
delay(1000);
tft.fillScreen(TFT_WHITE);
classification_flag = 2;
}


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{
Serial.println("");
Serial.print("JOIN failed!\r\n\r\n");
delay(5000);
}
}
else{
char cmd[128];
sprintf(cmd, "AT+CMSGHEX=\"%08X %08X\"\r\n", classification_flag);
ret = at_send_check_response("Done", 10000, cmd);
if (ret){
Serial.print("classification_flag:");
Serial.print(classification_flag);
Serial.print("\t");
recv_prase(recv_buf);
}
else{
Serial.print("Send failed!\r\n\r\n");
}
delay(5000);
}
}
else
{
delay(500);
}

}

#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_ACCELEROMETER
#error "Invalid model for current sensor"
#endif

Hazlo tú mismo para más funciones

note

Para más detalles, consulta la documentación a continuación.

Algo a lo que podríamos prestar más atención:

  • Almacenar resultados de clasificación:

    Podemos establecer un umbral para cambiar nuestras banderas cuando una cierta condición esté presente, y diferentes categorías reciben diferentes etiquetas.

    Podemos comentar la función tft para velocidad.

    int classification_flag = 0;
    if (result.classification[1].value > 0.7) {
    tft.fillScreen(TFT_PURPLE);
    tft.setFreeFont(&FreeSansBoldOblique12pt7b);
    tft.drawString("Wave", 20, 80);
    delay(1000);
    tft.fillScreen(TFT_WHITE);
    classification_flag = 1;
    }
    if (result.classification[2].value > 0.7) {
    tft.fillScreen(TFT_RED);
    tft.setFreeFont(&FreeSansBoldOblique12pt7b);
    tft.drawString("Circle", 20, 80);
    delay(1000);
    tft.fillScreen(TFT_WHITE);
    classification_flag = 2;
    }
    ....
  • Bloque de código de envío de datos:

    Con la red Lora disponible, podemos usar la función para enviar la etiqueta a Helium y recuperarla mediante el Decodificador que escribimos en Helium.

    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{
    Serial.println("");
    Serial.print("JOIN failed!\r\n\r\n");
    delay(5000);
    }
    }
    else{
    char cmd[128];
    sprintf(cmd, "AT+CMSGHEX=\"%08X %08X\"\r\n", classification_flag); // Change classification_flag to data wanna transfer
    ret = at_send_check_response("Done", 10000, cmd);
    if (ret){
    Serial.print("classification_flag:");s
    Serial.print(classification_flag);
    Serial.print("\t");
    recv_prase(recv_buf);
    }
    else{
    Serial.print("Send failed!\r\n\r\n");
    }
    delay(5000);
    }
    }
    else
    {
    delay(500);
    }

Soporte Técnico y Discusión del Producto

¡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.

Loading Comments...