Skip to main content

Edge Impulseを使用した内蔵センサー

note

この文書は AI によって翻訳されています。内容に不正確な点や改善すべき点がございましたら、文書下部のコメント欄または以下の Issue ページにてご報告ください。
https://github.com/Seeed-Studio/wiki-documents/issues

Heliumを介してクラウドにEdge Impulseアプリケーションを開発

産業用センサーへのアップグレード可能

SenseCAPのS2110コントローラーS2100データロガーを使用すると、Groveを簡単にLoRaWAN®センサーに変えることができます。Seeedはプロトタイピングを支援するだけでなく、SenseCAPシリーズの堅牢な産業用センサーを使用してプロジェクトを拡張する可能性も提供します。

IP66ハウジング、Bluetooth設定、グローバルLoRaWAN®ネットワークとの互換性、内蔵19Ahバッテリー、そしてアプリからの強力なサポートにより、SenseCAP S210xは産業用途に最適な選択肢です。このシリーズには、土壌水分、空気温度と湿度、光強度、CO2、EC、そして8-in-1気象ステーション用のセンサーが含まれています。次の成功する産業プロジェクトに最新のSenseCAP S210xを試してみてください。

SenseCAP 産業用センサー
S2100
データロガー
S2101
空気温度 & 湿度
S2102
S2103
空気温度 & 湿度 & CO2
S2104
土壌水分 & 温度
S2105
土壌水分 & 温度 & EC
S2110
LoRaWAN® コントローラー
S2120
8-in-1 気象ステーション

使用するツール

note

このセクションを始める前に、Wio Terminal の製品について理解していることを確認してください。 詳細については、以下をお読みください:

この記事では、Edge Impulse を使用してモデルを生成し、クラウドに接続したい人向けのソリューションを紹介します。デモでは Google スプレッドシートを使用します。これは直接的で簡単な方法です。

Helium の設定

ステップ 1. Google フォームをサポートする統合を作成する

このステップは、記事 Helium を介した Google スプレッドシートへの統合 の手順と似ています。

必要な作業は、統合に名前を付けて設定を保存するだけです。

Google フォームへの接続:

  • 作成

  • Google スプレッドシートとの接続

  • Google フォーム ID とのリンク

ステップ 2. Google フォーム API とデコーダ機能を使用した関数を作成する

Google フォームが関数に接続され、上記の手順で取得した ID が入力されていることを確認してください。

データフロー転送のためにデコーダサポートを備えた関数を以下のように作成する必要があります。

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

// 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("&");
}
// 削除禁止: Google フォーム関数

ステップ 3. フローを設定する

接続を確保する

Edge Impulse 設定

note

詳細については、以下をお読みください: Wio Terminal Edge Impulse Continuous Motion Recognition with Built-in Accelerometer

Arduino (Wio Terminal) 設定

note

センサーや環境が異なるため、異なるキャリアボードでトレーニングされたモデルを直接書き込むことは必ずしも理想的ではありません。信頼性の高いモデルはユーザー自身がトレーニングする必要があるため、ここでは迅速な体験のためのテストコードのみを提供します。

体験してみる

Edge Impulse からライブラリを生成した後、Wio Terminal で LoRa を介してデータを送信するためにコードを修正する必要があります。もし単に体験したいだけであれば、以下のコードをコピーして Arduino IDE を使用して Wio Terminal にフラッシュしてください。

以下のテストコードをフラッシュします。

#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);
}
}
////// メッセージ送信ブロック終了


/* 定数定義 -------------------------------------------------------- */
#define CONVERT_G_TO_MS2 9.80665f
#define MAX_ACCEPTED_RANGE 2.0f // 2022年3月以降、モデルは+-2の範囲で生成されますが、この例では+-4gの範囲を設定するArduinoライブラリを使用しています。古いモデルを使用している場合は、この値を無視して4.0fを使用してください。

/* プライベート変数 ------------------------------------------------------- */
static bool debug_nn = false; // 生の信号から生成された特徴などを確認するには、これをtrueに設定します。

/**
* @brief Arduino setup 関数
*/
void setup()
{
// 初回実行時にここにコードを記述します:
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); // 出力データレートを25Hzに設定、最大5kHzまで設定可能
lis.setFullScaleRange(LIS3DHTR_RANGE_16G); // スケール範囲を2gに設定、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 数値の符号を返します
*
* @param number
* @return int 正の数(または0)の場合は1、負の数の場合は-1
*/
float ei_get_sign(float number) {
return (number >= 0.0) ? 1.0 : -1.0;
}

/**
* @brief データを取得して推論を実行します
*
* @param[in] debug trueの場合、デバッグ情報を取得します
*/
void loop()
{
ei_printf("\nStarting inferencing in 2 seconds...\n");

delay(2000);

ei_printf("Sampling...\n");

// IMUから読み取る値のためにここでバッファを割り当てます
float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };

for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) {
// 次のティックを決定(その後スリープ)
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());
}

// 生のバッファを信号に変換し、それを分類します
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;
}

// 分類器を実行
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;
}

// 予測結果を出力
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

DIYでさらなる機能を追加

note

詳細については、以下のドキュメントをご覧ください。

注意すべき点:

  • 分類結果の保存:

    特定の条件が存在する場合にフラグを変更するための閾値を設定できます。また、異なるカテゴリには異なるラベルを付けることができます。

    処理速度を向上させるために、tft関数をコメントアウトすることができます。

    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;
    }
    ....
  • データ送信コードブロック:

    Loraネットワークが利用可能な場合、タグをHeliumに送信し、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); // 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);
    }

技術サポートと製品ディスカッション

弊社製品をお選びいただきありがとうございます!製品をスムーズにご利用いただけるよう、さまざまなサポートを提供しています。異なる好みやニーズに対応するため、複数のコミュニケーションチャネルをご用意しています。

Loading Comments...