M5stackからUDPでラズパイにメッセージ送信

M5stackをIoT端末として使うときにサーバー(この場合はラズパイ)にメッセージを送る必要がありますが、Wi-Fiが一番汎用性がありそう。

というわけで、UDP使ってメッセージを送ってみます。ラズパイ側はサーバーにNode.js使うので同じくNode.jsスタイルで記述します。

<M5stack側>

NTPサーバー使って現在時刻の取得、mDNS機能を使ってラズパイのアドレスを検索しています。参考サイトは、以下の通りです。

・タイムサーバーの使い方

https://kuracux.hatenablog.jp/entry/2018/10/08/160000

・IPアドレスを4オクテット記法に変換する

https://qiita.com/dojyorin/items/ac56a1c2c620782d90a6

#include <m5stack.h>
#include <WiFi.h>
#include <wifiudp.h>
#include <espmdns.h>


// Wi-Fi connection info.
const char* ssid     = "your said";
const char* password = "your pw";
const int port = 3002;

String string_buf = "message from M5stack";

//ntp server and time info
const char* ntpServer =  "ntp.nict.jp";
const long  gmtOffset_sec = 9 * 3600;
const int   daylightOffset_sec = 0;
struct tm timeinfo;

// The udp library class
WiFiUDP udp;

// mDNS target & M5stack
const String target_dev = "raspberrypi";
IPAddress ip;         // IPAdress of the target device
String ip_addr = "";  // 4 octet style target address
const char *mdns_name = "m5stick";  //M5stack mDNS name

// convert IP address to 4 octet style
String ipToString(uint32_t ip){
  String result = "";

  result += String((ip & 0xFF), 10);    // base number : 10
  result += ".";
  result += String((ip & 0xFF00) >> 8, 10);
  result += ".";
  result += String((ip & 0xFF0000) >> 16, 10);
  result += ".";
  result += String((ip & 0xFF000000) >> 24, 10);

  return result;
}

void mdns(){
  mdns_init();
  ip = MDNS.queryHost(target_dev);
  // target dev IP
  Serial.println("");
  Serial.print(target_dev + " : ");
  Serial.println(ip);
  // M5 self IP
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  ip_addr = ipToString(ip);
  // register mDNS name
  if (MDNS.begin(mdns_name)) {
    Serial.println("MDNS responder started");
  }
}

void print_wifi_state(){
	M5.Lcd.clear(BLACK);  // clear LCD
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(3, 3);
	M5.Lcd.println("");
	M5.Lcd.println("WiFi connected.");
	M5.Lcd.print("IP address: ");
	M5.Lcd.println(WiFi.localIP());
	M5.Lcd.print("Port: ");
	M5.Lcd.println(port);
}

void setup_wifi(){
	M5.Lcd.setTextColor(RED);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(3, 10);
  M5.Lcd.print("Connecting to ");
  M5.Lcd.println(ssid);

	// setup wifi
  WiFi.mode(WIFI_STA);  // WIFI_AP, WIFI_STA, WIFI_AP_STA or WIFI_OFF
  WiFi.begin(ssid, password);
	// WiFi.begin();

	// Connecting ..
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    M5.Lcd.print(".");
  }

	// print state
  print_wifi_state();
  udp.begin(port);
  
  // mDNS
  mdns();
}

void setup() {
	M5.begin();
  Serial.begin(115200);
	// setup wifi
	setup_wifi();
    //init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop(){
    // get current rime
    if (!getLocalTime(&timeinfo)) {
      Serial.println("Failed to obtain time");
    return;
    }
    String cur_time = String(timeinfo.tm_year + 1900) 
      + "/" + String(timeinfo.tm_mon) 
      + "/" + String(timeinfo.tm_mday) 
      + " " + String(timeinfo.tm_hour) 
      + ":" + String(timeinfo.tm_min) 
      + " " + String(timeinfo.tm_sec) + " ";
    Serial.println(cur_time);

    //udp send
    const char *ipadr = ip_addr.c_str();
    udp.beginPacket(ipadr, port);
    // current time
    int i = 0;
    while( cur_time.length() > i){
      char tmp = cur_time.charAt(i);
      udp.write(tmp);
      i++;
    }
    // contents
    i = 0;
    while( string_buf.length() > i){
      char tmp = string_buf.charAt(i);
      udp.write(tmp);
      i++;
    }
    udp.endPacket();
    delay(1000);
}

 

<ラズパイ側>

Node.js配下のスクリプトです。

const dgram = require('dgram');
const port = 3002;
const client ='0.0.0.0';

const socket = dgram.createSocket('udp4');

socket.bind(port, client);

socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);
});

参考サイトは、

・ラズパイのavahiユティリティのインストールと使い方

$ sudo apt-get install avahi-utils

Wi-Fi内のデバイスリスト

$ avahi-browse -at

・Node.jsのUDP通信ドキュメント

https://runebook.dev/ja/docs/node/dgram

・what’s socket.io

https://socket.io/docs/v4/

ラズパイ側の出力、

 

admin

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

何かの拍子、おそらく異なるラズパイにログインした時とか、に出るsshログインできない現象。

解決策は、

% rm ~/.ssh/known_hosts
% ssh pi@raspberrypi.local
The authenticity of host 'raspberrypi.local (240d:1a:896:8300:acf1:ba5:2b9e:7977)' can't be established.
ED25519 key fingerprint is SHA256:JkCfuJ3qZs3XON3vOOkf2M7RP5HAXNWwSxAnNG0dqck.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'raspberrypi.local' (ED25519) to the list of known hosts.

このように、known_hostsを削除することによって、sshログインが可能となります。

 

admin

MLX90614 thermal sensor(M5stack)

センサーシリーズの第三段目の赤外線温度センサー。この際、センサー種類を切り替える都度ソフトダウンロードが面倒なので、複数のセンサー(今現在、レーザー距離計、熱中症アラームセンサーと赤外線温度センサー)に対応できるようにコードを書き換え。

具体的にはsetup()処理の中で、センサーの初期化コマンドを出して正常にレスポンスが返って来たものをターゲットにして、loop()処理中で呼び出す処理を振り分けします。以下はsetup()処理だけ切り出し、

void setup(void)
{
  M5.begin();             // Init M5Stack
  M5.Power.begin();       // Init power  
  Wire.begin();           // Wire init, adding the I2C bus.
  Serial.begin(115200);

  if (setup_dist() == 1){
    dev_id = 1;
  } else if (setup_env() == 1){
    dev_id = 2;
  } else if (setup_thermo() == 1){
    dev_id = 3;
  }
  else{
    while(1){
      sensor_error();
      delay(1000);
    }
  }
}

温度センサーはライブラリ、

https://github.com/adafruit/Adafruit-MLX90614-Library

をダウンロードして使用。

使ってみた感じは、非接触ではあるけれどもターゲットにかなり近寄らないときちんとした値が取れないようです。

————————

P.S. 2022/11/24

MLX90614には視野角で何種類かあって、このモデルはおよそ90度なのでその視界に入るオブジェクトの平均値が表示されるからです。

————————

こんな感じに、

全体のコードは以下のリンクで、

https://github.com/chateight/multi_sensor_hdl

 

admin

M5stackで使えるメモリ容量を大きくとりたい

M5stackでビルドした時に必要メモリサイズが出力されますが、なぜか搭載メモリ(Flash)サイズに比較してずいぶん小さい。

やりたいことは色々なセンサーの種類をsetup()ルーチン中で判別して必要なコードにloop()から呼び出しをするということなので、センサーの種類が増えていくとメモリをどんどん消費していくから。

写真の場合にはmDNSを使おうとした場合ですが、ライブラリのサイズが大きいようで既に6割以上を消費しています。

実際のメモリは16MB搭載しているはずですが、1.3MBぐらいしか使えないことになっています。もちろん基本のファームウェア領域も存在するんだろうけど、それにしては少なすぎ。

これを拡張するには、ボードの種類をM5Stack-Core-ESP32からM5Stack-fireにすると拡大します。ただしこれでも見えるサイズは6.5MBぐらいなので搭載メモリの半分以下ですが、5倍に拡大されたと思えば大幅改善で、さらにRAM領域はおよそ10倍に拡張されているので、おそらくRAM領域は不足するような事態には特別にRAM領域を消費するようなコードを書かない限りは大丈夫そうです。

ボード種類を変えることによる副作用は今のところ見えていません。このやり方はArduino IDEに限らずVScodeのPlatformIOでも同じです。

M5stackでもコードは起動後は不変だからFlash領域を使用して、変数やスタック領域などの書き換え必要な領域にはRAM領域を使用するようになっています。

 

admin

 

 

 

M5stackでmDNSを扱う

同じWi-Fiエリアで例えばラズパイと通信しようとすると固定IPは使わないだろうからアドレス解決にmDNSを使う必要があります。ラズパイ側はavahiでmDNSがデフォルトで使えるようになってますね。

参考サイトは、

https://shuzo-kino.hateblo.jp/entry/2021/02/22/230207

・公式ドキュメント

https://espressif.github.io/esp-protocols/mdns/en/index.html

を参考に、動かしてみました。

mDNSをサポートするライブラリは”espmdns.h”になります。

#include <m5stack.h>

#include <wifi.h>
#include <wificlient.h>
#include <webserver.h>
#include <espmdns.h>

const char* ssid = "said"; // set your said
const char* password = "pw"; // set your pw

String target_dev = "NODE-1cdc";

WebServer server(80);

void handleRoot() {
  server.send(200, "text/plain", "hello from M5stack server using mDNS!");
}

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void setup(void) {
  M5.begin(true, false, true);
  M5.Power.begin();       // Init power  
  M5.lcd.setTextSize(2);  // Set the text size to 2. 
  M5.Lcd.setTextColor(WHITE);     // set font color to white
  M5.lcd.fillScreen(BLACK);
  delay(500);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  M5.lcd.setCursor(50, 110);
  M5.Lcd.print("connected to Wi-Fi");

  mdns_init();
  IPAddress ip = MDNS.queryHost(target_dev);
  Serial.println("");
  Serial.print(target_dev + " : ");
  Serial.println(ip);

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("m5stack")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);

  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
}

・HTTPサーバーとしてのmDNS機能の確認

M5stackにHTTPサーバー機能を持たせてそのアドレス検索にmDNSを使っています。

・ネットワーク内のmDNSデバイスのIPアドレス検索

53行目から57行目がそのコードになります。

・M5stackにはWi-Fi接続完了時にメッセージ出しています

 

admin

 

Arduino IDEでM5stackを扱う

今更のメモですが、

簡易的にM5stackを扱うのにはArduino IDEが便利です、その理由はI2Cデバイスのライブラリが整っているので、わざわざライブラリを組み込む手間が省けると言うのが一番大きいし、VScodeはビルドの時にパスを通すように設定とか必要になるから。

あと、シリアルポートの出力結果を簡単にグラフ表示できると言うのもありますね。

環境のセットアップ手順は既に作成されている資料で完璧です。

https://qiita.com/hmmrjn/items/2b2da09eecffcbdbad85

USBドライバーは最近新しいバージョンになっていますが。

 

admin

ラズパイでWebSocketの環境構築

基盤はNode.jsを使うことにしたので、必要なパッケージをインストールします。

Node.js他のインストールは以下のリンクを参考に、

https://it-evo.jp/blog/blog-813/

<Node.jsとnpmをインストール>

$ sudo apt install -y nodejs npm

途中でエラーになったので、

$ sudo apt-get update

この後再び、

$ sudo apt install -y nodejs npm

版数、

$ node -v
v12.22.12
$ npm -v
7.5.2

バージョン管理パッケージ(n)のインストール

$ sudo npm install n -g
added 1 package, and audited 2 packages in 19s
found 0 vulnerabilities

今の版数、

$ n -V
v9.0.1

長期安定版のインストール、

$ sudo n lts

安定版の方が版数古いけど、版数戻し、

$ node -v
v12.22.12

$ hash -r
$ node -v
v10.24.1

古い(安定版じゃない)パッケージを削除、

$ sudo apt purge -y nodejs npm

<Express/Express-Generatorのインストール>Express : Node.jsで利用できるWebアプリケーションフレームワーク

$ npm install express --save

Express-generatorはルート権限でないとインストールできなかった、

$ sudo npm install express-generator -g
$ express --version
4.16.1

P.S. 2022/11/25

npmでのインストールパラメータ “–save”は現在のnpmでは暗黙に指定されている様です。

<webサーバーの立ち上げ>

$ express t_web --view=pug
$ cd t_web
$ npm install

t_webのディレクトリ構成

見かけon Railsに雰囲気似ています。拡張子pugはHTMLを発生させるテンプレートです。views/index.pugがトップページだろうと思って中身を見ると、確かに作成されるHTMLを連想できるような内容になってます。

$ ls -l
total 68
-rw-r--r--   1 pi pi  1074 Nov 15 04:47 app.js
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 bin
drwxr-xr-x 123 pi pi  4096 Nov 15 04:52 node_modules
-rw-r--r--   1 pi pi   297 Nov 15 04:47 package.json
-rw-r--r--   1 pi pi 38995 Nov 15 04:52 package-lock.json
drwxr-xr-x   5 pi pi  4096 Nov 15 04:47 public
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 routes
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 views
~/t_web $ ls -l views
total 12
-rw-r--r-- 1 pi pi  84 Nov 15 04:47 error.pug
-rw-r--r-- 1 pi pi  66 Nov 15 04:47 index.pug
-rw-r--r-- 1 pi pi 125 Nov 15 04:47 layout.pug
~/t_web $ cat views/index.pug
extends layout
block content
  h1= title
  p Welcome to #{title}

<サーバー起動>

ディレクトリt-webでnpm startで起動してブラウザからアクセスすると、アクセスのログがコンソールに出力されます。

$ npm start
> t-web@0.0.0 start /home/pi/t_web
> node ./bin/www
GET / 200 9066.188 ms - 170
GET /stylesheets/style.css 200 89.833 ms - 111
GET /favicon.ico 404 432.286 ms - 962
GET / 200 371.813 ms - 170
GET /stylesheets/style.css 304 22.612 ms - -
GET / 200 293.274 ms - 170

初回のアクセスだけは展開処理が入るようで9秒程度かかっていますが、以降はキャッシュ使うので早い。

pugファイルから予想される内容のページが表示されます。

アクセス方法は、

http://url:3000

urlはローカルIPアドレスです。

ネットワークアクセスは有線LANなのでランプの点滅で確認可能。

 

admin

Raspberry Pi Imager(最近変わった?)

もしかしたら忘れてるだけかもしれませんが、Raspberry PIへのイメージ書き込みツールでsshを有効にしないと、起動してもあらゆるポートが閉じている(nmapで開いてるポートが確認できない)ので何もできません。

一年前はsshという名前の空ファイルをトップディレクトリに入れてましたが。

https://isehara-3lv.sakura.ne.jp/blog/2021/10/06/大昔のraspberry-pi-bに最新イメージを入れてみた/

上のやり方も相変わらず有効かもしれないけど、書き込み設定でできればそちらの方が簡単。

sshを有効にするには、Imagerの設定(右下)をクリックすると画面のような設定画面がポップアップされるので、ここでsshの有効化とユーザー名(デフォルト”pi”)とパスワード(例えば”raspberrypi”)を設定してからイメージを書き込みます。

% nmap 192.168.1.14  
Starting Nmap 7.92 ( https://nmap.org ) at 2022-11-14 20:04 JST
Nmap scan report for 192.168.1.14
Host is up (0.11s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh

これで、sshのポートだけは開きました。

ついでながら、rootログオンのためには、

$ sudo passwd root

でrootパスワードを設定します。

 

admin

 

 

netcatの使い方の基本

ネットワークの診断を特にスクリプトとか作らなくてもコマンドラインで実行できるプログラム。

以下の例は同じマシンでやっているので、ほぼ無意味ですが使い方はこんな感じです。netcatはMacには最初からインストールされているようです。

サーバー側で、

% nc -ul 8888

で受信待ち

クライアント側で、

% nc -u 127.0.0.1 8888

で適当に文字を打てば、サーバー側で受信します、文字コードの意識なく日本語もそのまま、同じマシンだから

 

より具体的には、自宅の同じWi-Fiグループに存在しているラズパイの音楽サーバーにアクセスしてみると、

% nc -v 192.168.1.8 80

connection — が返るから

GET / HTTP/1.1

と、改行を入力するとHTTPレスポンスヘッダー以下が返ってきます。

% nc -v 192.168.1.8 80
Connection to 192.168.1.8 port 80 [tcp/http] succeeded!
GET / HTTP/1.1

HTTP/1.1 200 OK
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Wed, 06 Oct 2021 10:24:56 GMT
ETag: W/"e64-17c55227cc0"
Content-Type: text/html; charset=UTF-8
Content-Length: 3684
Vary: Accept-Encoding
Date: Fri, 11 Nov 2022 07:26:42 GMT
Connection: keep-alive

<!doctype html>
〜〜以下省略〜〜

仮にサーバーを建てることもできるので、ネットワーク系の診断には便利なコマンドです。ただし、自分が管理している以外のサーバーにはポートスキャンと同じくアクセスすべきではないでしょう。

 

admin

c/c++におけるerrno値の定義

CtelloのGitHubのソースをビルドして実行すると以下のようなエラーが出ました。

最初は何気なくTello sdk 2.0用だからと思ってましたが、考えてみれば引数のエラーだから2.0は無関係。

cello.cppで、

#include <errno.h>

されているので、errno.hの中身を追跡するとエラー番号一覧がわかるはず。

コードの中では、

strerror(errno)

を使ってデコードしてますが、

[11/09/22 12:28:51] [ctello] [info] Finding Tello ...
[11/09/22 12:28:51] [ctello] [error] sendto: 22 (Invalid argument)
[11/09/22 12:28:52] [ctello] [error] sendto: 22 (Invalid argument)
[11/09/22 12:28:53] [ctello] [error] sendto: 22 (Invalid argument)

 

基本のエラー番号をまとめてるサイトがあって、

https://qiita.com/h2suzuki/items/0cc924cdd9d5c6d47448

こちらは有用だと思います。

肝心の引数エラーの原因調査はこれから。

 

admin