バランスロボット

二輪で、本体の傾きをセンスしてバランスをとりながら倒れないように制御するロボット(倒立振子というらしい)ですが、イメージとしては秋田の竿燈みたいなものか。竿燈は二次元の制御だけど、二輪ロボットは一軸制御だから制御は相対的には楽そうに見えます。

回転モーメントが大きいほど、つまり高さ方向が長いほどフィードバック時間に余裕がありそうに思えますね。

原理は、

https://matome.eternalcollegest.com/post-2136401209031725401

完成品としては、

https://docs.m5stack.com/en/app/bala2fire

や、

https://www.switch-science.com/products/8297

あたりがありそうですが、M5stack使って自作するというのもありそうです。

例えばこれとか、

https://maker.pro/arduino/projects/build-arduino-self-balancing-robot

PID制御はカットアンドトライでパラメータを決めるのが現実らしい。PIDライブラリというのがArduinoには用意されていて、Arduino IDEのライブラリで検索するとたくさん出てきます。

 

admin

M5stackでTelloを制御

M5stackでUDPが使えるから、Telloのコマンド制御もできるよねと思いましが、既に先人のコードができてます。

https://qiita.com/Mitsu-Murakita/items/b86ad79d3590adb3b5b9

TelloのSSIDだけを追加すれば動きましたが、動作がイマイチもっさり、というのはスイッチの反応が遅くて多少長押ししないとtake offもしない。

で時間待ちの500msを150msにするとほぼイメージ通りに動きますが、疑問点が一つ。

wasPressed()関数って、ボタン押されたことを記憶できる関数のはずなのになぜ長押ししないといけないか?

loop()の中でボタン機能の確認コードを作って確認してみると、どうもdelay()中にボタンを押してもwasPressed()には反映されない模様です、だからdelay()で設定した時間以上にボタンを押していないといけないようです、仕様として変だと思いますが。

~~~
  if ( M5.BtnA.wasPressed() ) {
    Serial.println("BtnA.wasPressed() == TRUE");
  }
  //Serial.println("loop");

  delay(300);
~~~

 

あとフリップ動作は、イマイチスイッチを押すタイミングとM5stackを傾けるタイミングが掴めない。

 

admin

オランダの320Kbpsラジオ局

ベートーベン・チャネルとは言ってますが、正確にはベートーベン同時代古典派の作曲家の曲を流すと言った方が正確でしょう。間違ってもロマン派とかイタリア、ロシアの作曲家の曲は流れてきません。

MozartやHydnも流れてきますが、画像にあるようにオランダオーケストラの曲で時々のアナウンスでもNetherlandからと言ってます。おおむねどこのラジオ局でもラジオ局が属する国の楽団の演奏を流すのは、自国の文化の宣伝という意味のほかに著作権料でも優遇されるからなのかも知れません。

 

admin

Nodeと関連技術

Nodeに関連して検索したサイトと自分用のメモです。

 

○ WebSocketのURL形式

通信時に指定するURLは、

ws://www.sample.com、あるいはセキュア接続ならwas://www.sample.com

明に使うことはあまりないだろうけども

 

○ Node.jsでソケット通信

<socket.ioのインストール>(アプリを作成するディレクトリで)

% npm install socket.io 

もはやdgramはインストール不要らしい

 

○ websocket動かすには

Node.jsとExpressの他にwsライブラリのインストールも必要、本当?

<HTTP serverとclientサンプル>

socket.ioを使った、

以下の二つはGitHubのサンプル動かしているのでセット

https://qiita.com/kouji0705/items/cf16044c7d825d09d707

https://github.com/neroneroffy/webSocketDemo

https://ai-soldier.work/websocket-node-ws/#

これ動かしてみたけど、ラズパイでも軽快に動く

 

○ Express

<documents>

https://expressjs.com

<解説>

https://qiita.com/mml/items/3cc90479df033c0998e4

https://qiita.com/ganyariya/items/85e51e718e56e7d128b8

<起動>

$ npm start

 

○ pug(HTMLテンプレート)について

<pugの概要>

https://and-ha.com/coding/what-is-pug/

こんなこともできる(markdownをincludeしてHTML出力)

https://ytyaru.hatenablog.com/entry/2018/05/01/000000

<pugの拡張>

https://qiita.com/zenno04/items/d16f881170170b567b16

Template Inheritance

https://pugjs.org/language/inheritance.html

 

○ wsサーバーの起動

Express serverとWebSocket severそれぞれ別に起動で良い

$ node ws_server.js

実際の運用は起動スクリプトに書くから個別起動で問題ない

 

○ Javascript(chart.js)でグラフ作成

json使いましょう

https://www.web-development-kb-ja.site/ja/javascript/chartjsでjsonデータを表示する/833307193/

 

○ JSからEJSへ

https://fukuno.jig.jp/2819

use strict : ESM(ES module)有効なら指定不要だったよね

 

admin

ラズパイでWebSocket通信(その3)

 

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はセッションが有効な時しか働かないので、セッションが切れればデータは更新されません。

ブラウザ画面の表示はこちらを、

m5stack_ws – 720WebShareName

 

admin

Davide of mimic

これもネットラジオ曲ですが、イギリスからの高音質(320Kbps)のラジオ。

https://www.davideofmimic.com

しばらく中断していましたが、再開されてます。

広告なしでどうやって運営してるのかは気になるところ、オペーレーティング費用の他に著作権料の支払いも必要なわけだし。ドネーションだけで全て賄ってる?

無料のspotifyは音質イマイチで広告入るから最近あまり使わない。サブスクするまでも無さそうだし、

 

admin

classical netradio 320Kbps

Linn radioは数ヶ月不通状態継続、もうサービス止めるのか?、なので320Kbpsの高品質ラジオ局を改めてVolumioから抽出してみた。

その中に”100 GREATEST CLASSICAL MUSIC”というのがあって、超定番の100曲を流し続けているらしいラジオ局。しばらくすれば飽きるだろうけど、しばし選択。

 

admin

ラズパイでWebSocket通信(その2)

ラズパイでWebSocketの環境構築

の続編です。

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から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