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

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

 

 

co2センサー(MH-Z19)のキャリブレーション

測定値が、風通しの良い場所でも800ppmとか高い値になっていて、場所を変えてもダメだったのキャリブレーションしてみた。

https://github.com/UedaTakeyuki/mh-z19/wiki/CALIBRATION-&-detection-range

Pythonライブラリ関連の情報の中にキャリブレーションツールも用意されているのでそれを使いました。

co2センサーはシリアルでRaspberry PIとつながっているのでroot権限で実行しないといけませんが、

$ sudo python -m mh_z19 –zero_point_calibration

Call Calibration with ZERO point.

で実行できました。多少低すぎの感もありますが、いずれまたズレていくんじゃないか(以下の③によって)と思います。

ソースを見ると、以下で規定されるゼロキャリブレーション方法の②のコマンドによって実行しています。

https://www.winsen-sensor.com/d/files/MH-Z19B.pdf

ゼロキャリブレーションには① ハード的にピンを一時的に短絡、② コマンド送信、③ 自動キャリブレーションの三つの方法があるとなっていますが③の有効/無効化もコマンドで対応できるのでライブラリから指定できるようになっています。

また、コマンドパラメータに-hでヘルプ、何も付けないと辞書型({“co2”: 414} のように)で測定値が返ります。

ゼロキャリブレーション以外にスパンキャリブレーションというのもありますが、これはスケール精度、つまり1000ppmや2000ppmの値の倍率(スケール)精度用ですが、正確な1000ppm環境など作れないから省略。

P.S. 2022/9/18

限りなく外気に触れる場所で測定値の収束時間を見てみました。

 

保存していた部屋(おそらくco2濃度1000ppm近く)から無人の部屋の窓を開けて、窓辺に放置して時間経過と測定値。おそらく最初は部屋のガスが取り込まれて高い値を示していますが、徐々に大気の値に近づいていきます。おおむね20分放置しろとデバイスのマニュアルに書かれてますから、初期値が高かったらその程度の放置時間は必要そうです。

 

admin

 

Ajaxでco2センサーのデータ表示

ほぼ一年前に作ったco2測定データロガー、webでも結果が見れるようにしているのですがリフレッシュを自動でしないので、ブラウザで更新しないといけないから多少不便。リロード指定でもできるけれどもスマートさが無い、実行時間などで実際にはこの程度のページならなんら問題ないにしても。

と言うことでAjax使って自動更新するようにしてみた。

実行は、jqueryを使うのがコード量が少なくて良いだろう。

・全体の構成は、DBから計測値取得のphpファイルは複数箇所から呼び出すから別ファイル(data_read.php)にする。

・Apache document rootは/var/www/html/だから、

$ cp index.php /var/www/html/

のようにユーザエリア内のコード修正後はユーザディレクトリからコピーする。

やったことは以下の二点

① DBから定期的に最新の測定値を読み出して、測定値タグのclassに書き込み

② スタイルシートを同時にjsで変更してやる

の二点です。

 

過去のブログはこの辺り、

https://isehara-3lv.sakura.ne.jp/blog/2021/09/20/co2-センサー(ndirとは、non-dispersive-infrared(非分散型赤外))mh-z19bを/

現状のソースコードは、

https://github.com/chateight/co2_sensor/tree/main/co2_sensor

 

P.S. (2022/8/31)

jsのコードがスマートさに欠ける(特にスタイルシート変更部分)ので修正。

 

admin

ステッピングモーター納期遅延

最近Amazonは中華販売サイト化していて、商品出荷も中国から直接が多いのですが、DJIのドローンもそうだったけれども、これも遅延してます。

そう入っても数日の問題だろうから、来月初めには到着するでしょう。

 

admin

volumioプレイヤに蓋作成

ステータス表示が見えるので半裸状態だったvolumioプレイヤ(ラズパイゼロ+allo dac)に蓋を作成。もともとあったはずだけど気に入らないから捨ててしまっていた模様。このケースもともとラズパイをケースに納めようとすると、sdカードを浮かせてsdカードコネクタ破壊するような寸法(納入時点で 既にsdカードコネクタの半田付け浮いててラズパイ一枚廃棄)で追加工必要だった過去があるし。

現物合わせで、fusion360でcad図面を起こして、3dプリンタで造形。

フロントパネルとの羽目合いもピッタリの感じです。

audioジャックのセンターズレはもともと購入時点から。

 

admin