相手がRaspberry PIの時ですが、ラズパイ側のターミナルからMacのファイルを指定するとMacのセキュリティを緩めないといけないから、Mac側のターミナルからアクセスするのが正しいだろうと思う。相手次第ですがね、
こんな感じで、
% scp -r /Users/ファイル or フォルダ指定 pi@raspberrypi.local:~/
本来ならafp使いたかったけど、何故か動かないから代替えでファイル転送使う時に感じたこと。
admin
la vie libre
相手がRaspberry PIの時ですが、ラズパイ側のターミナルからMacのファイルを指定するとMacのセキュリティを緩めないといけないから、Mac側のターミナルからアクセスするのが正しいだろうと思う。相手次第ですがね、
こんな感じで、
% scp -r /Users/ファイル or フォルダ指定 pi@raspberrypi.local:~/
本来ならafp使いたかったけど、何故か動かないから代替えでファイル転送使う時に感じたこと。
admin
https://isehara-3lv.sakura.ne.jp/blog/2022/11/29/ラズパイでwebsocket通信(その2)/
の続編で、
① M5stackからラズパイにUDPでデータ送信して、
② ラズパイとクライアント間でweb_socket使ってリアルタイムのデータ更新(今は5秒毎にM5stackから送信)、
③ 受信データをchart.js使ってグラフ化(データ最大5回分)、
します。
WebSocketで送られるデータ形式は汎用性考慮してjson形式を使っています。
全体のコードは、
M5stack側、
https://github.com/chateight/multi_sensor_udp_comm
ラズパイ側、
https://github.com/chateight/web_socket
となります。現状、測定値を送信するのはthermo_sensorのコードだけですが、他のセンサーでも同じルーチン(udp_loop(String))呼び出せば送信できます。
web_socketはセッションが有効な時しか働かないので、セッションが切れればデータは更新されません。
ブラウザ画面の表示はこちらを、
admin
の続編です。
Expressのプロジェクトのviews以下に以下のファイルを配置しました。pugファイルの継承機能の確認も兼ねています。block inheriの直下にblock contentがあるので、index.pugの内容が挿入されて結果として作成されるHTMLファイルは複数のpugファイルから構成されます。
layout.pug
--------------
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
<script src="./javascripts/ws_client.js" defer>
body
block inheri<
inheri.pug
--------------
extends layout
block inheri
block content
p Test site for M5stack data transfer using UDP & WebSocket
p#message.poor-writing ws test button
<input type="button" id="send_server" value="send" />
<div id="mes">web socket out put area</div>
index.pug
--------------
extends inheri
block content
h1= title
p #{title} is provided for timely data access
p ----------------------------------------------
クライアント側で動作するws_client.jsとサーバー側で動作するws_server.jsスクリプトです。いずれもpublic/javascriptsに配置してます。
ブラウザ上のbuttonを押すとサーバー側ではws_server.jsが検出して、サーバー側からレスポンスを返しています。
ws_client.js
--------------
const ws = new WebSocket('ws://raspberrypi.local:3002')
ws.onopen = e => {
console.log('Hello M5 server!')
}
ws.onmessage = e => {
const mes = document.querySelector("#mes");
mes.textContent = e.data;
console.log('received data:%s',e.data)
console.log(e)
}
ws.onerror = e => {
console.log('fail to connect:${e.data}')
console.log(e)
}
document.getElementById('send_server').addEventListener('click', () => {
ws.send('call back from the button')
})
ws_server.js
--------------
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({ port: 3002 });
wss.on('connection', ws_client => {
ws_client.send('good morning');
ws_client.on('message', data => {
console.log('send data: %s', data);
ws_client.send('received the data:' + data);
});
});
<起動>
Express:
$ npm start
WS:
$ node ws_server.js
別々に起動してもいいけど、おそらくExpress側で起動する様にできるはず。
以下の画面操作の動画です。
参考サイトは、
pugの継承
https://qiita.com/zenno04/items/d16f881170170b567b16
簡単ws使い方
https://ai-soldier.work/websocket-node-ws/
次にやりたいことはM5stackと以下のようにつなぐこと、Port 3000は共通ですが、UDPとTCP/IPの違いなので問題ありません。
admin
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
ラズパイ側の出力、
admin
何かの拍子、おそらく異なるラズパイにログインした時とか、に出る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へのイメージ書き込みツールで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
測定値が、風通しの良い場所でも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
ほぼ一年前に作った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://github.com/chateight/co2_sensor/tree/main/co2_sensor
P.S. (2022/8/31)
jsのコードがスマートさに欠ける(特にスタイルシート変更部分)ので修正。
admin
ちょっとしたはずみでSDカード割ってしまったの、最新版を焼き直し。
壁紙のデフォルトが有名な星雲になってましたが、名前は失念。
admin