2022年1月25日火曜日

2D LiDAR LDS-006を買ってみた。

 Aliexpressを見ていたらロボット掃除機のLiDARが10ドルで売ってたのでつい買ってしまった。送料いれて15ドルだった。

シールからはEcovacs RoboticsのLDS-006っていうセンサーだということがわかる。
レーザーToFセンサーによる回転式のLiDAR。仕様は不明…
一応台座ごとプチプチに包まれて届いたので届いた。台座が付いてるのでそのまま平面部に置ける。
センサー部を回すのはベルト駆動のDCモーター。ベルトなのでまあまあ静か。ちなみにセンサー部めっちゃ回してるけど、センサー部の電力はコイルで非接触で給電してる模様。
通信は回転中心に赤外線LEDとそれの受光部があってセンサーから台座には一方通行でシリアル通信してるみたい。
接続はPHコネクタっぽい。
分解画像とピンアサインはLDS-006 Lidarを参照した。
赤が5V、緑がRX、青がTX、黒がGND。baudrateは115200
とりあえずPHコネクタがピッタリ入った。ピッチが2.0mmなのでピンヘッダとかそのままさせないのよねぇ。UARTのレベルは3.3VらしいのでCP2102のUSBのドングルを使用した。5V電源もそちらからとった。

少し調べてみると中国語の情報だけどこのLDS-006を解析している人がいたので参考にさせてもらった。
Laser-Radar-LDS-006-Drive-Test
色んなサイトを見ているとモーターのコントロールを別にやらないとダメそうなんだけど試しにstartlds$コマンドを送ってみたら回ってデータも取れてそう。回転方向は反時計回りだった。
回り始めると22バイトずつのデータがめっちゃ流れてくる。おそらく4°ずつのデータかな。
ただ、距離は4個分入っているので分解能としては1°って言うことでいいのかなぁ?
とりあえず反応したコマンドは
startlds$
getserial$
holdlds$
ぐらいかな?holdlds$をすると止まる。ちなみに改行コードとかが入っていると次のコマンドを受け付けなくなるのでholdlds$したあとにstartlds$ができない場合は余計な文字が送られている可能性。
getserial$したら"L01500003131808091484"って帰ってきた。
CamsenseX1用に表示プログラムを作った人が公開してくださっていたのでこれをLDS-006のプロトコルに対応するようにテキトーに改造して試してみた。
とりあえず取れてそう。センサー本体を回すと平面部の角度が変化する。
距離はどのぐらい飛ぶんだろうなぁ
これを作ってから気がついたんだけど、通信プロトコルがNeato XV11 LIDARのfirmware v2.4とv2.6のプロトコルにそっくりである。もしかしてまんまそのままだったりして?
LDS-006の解析結果との違いは距離の2bytesのうちの上位2bit分がエラーflagとして使われているぐらいなもんだし。
掃除機の部品で角度の分解能がいまいちとはいえ、LiDARも安く手に入るようになったなぁ
カスタムファーム開発してる人もいるっぽいんだけど分解能とか改善できるのかね。

追記:LDS-006のプロトコルが実はXV11と一緒なんじゃないかというところが気になって後日距離データの上位2bit分を解析してみた。やっぱりXV11と同様、距離データは14bitでHigh byteの上位2bit分が0以外になるとLow byteがエラーコード?になるようで16bit目がHighになると距離のLow byteは0x88とか0x99固定になってた。
XV11用に開発されたツールとかも使えたりするんじゃないかな。

2022年1月22日土曜日

ESP32でバーチャルスティックを使ってみた。

 スマホゲームとかで画面上に指を置くとアナログのジョイスティックみたいに使えるバーチャルスティックをESP32で使ってみた。ESP32でWebサーバーを立てて、ブラウザ上のバーチャルジョイスティックからラジコンを操作してみた。


ブラウザ上でJavascriptによってバーチャルスティックを実現できるライブラリは古くから存在しているようで、今回はvirtualjoystick.jsを使わせてもらった。

このライブラリでは画面をタッチするとバーチャルスティックが出て座標が取得できる。このライブラリからESP32本体にデータを送るためにはWebSocketを使ってみた。

virtualjoystick.jsから出てきた座標データをESP32のWebSocketサーバーにJavascriptで投げる感じ。

<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		
		<style>
		body {
			overflow	: hidden;
			padding		: 0;
			margin		: 0;
			background-color: #BBB;
			overscroll-behavior-y: none;
		}
		#info {
			position	: absolute;
			top		: 0px;
			width		: 100%;
			padding		: 5px;
			text-align	: center;
		}
		#info a {
			color		: #66F;
			text-decoration	: none;
		}
		#info a:hover {
			text-decoration	: underline;
		}
		#container {
			width		: 100%;
			height		: 100%;
			overflow	: hidden;
			padding		: 0;
			margin		: 0;
			-webkit-user-select	: none;
			-moz-user-select	: none;
		}
		</style>

	</head>
	<body>
		<div id="container"></div>
		<div id="info">
			画面をタッチしてスライド
			<br/>
			<span id="result"></span>
			<br/>
			<span id="consoleArea"></span>
		</div> 
		<div id="consoleArea">sa</div>
<script src="./virtualjoystick.js"></script>
<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  window.addEventListener('load', onLoad);
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  function onOpen(event) {
    console.log('Connection opened');
  }
  function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
  }
  function onMessage(event) {
    var consoleArea = document.getElementById("consoleArea");
    consoleArea.innerHTML = event.data;
  }
  function onLoad(event) {
    initWebSocket(); 
  }
  function toggle(){
    websocket.send('toggle');
  }
			console.log("touchscreen is", VirtualJoystick.touchScreenAvailable() ? "available" : "not available");
	
			var joystick	= new VirtualJoystick({
				container	: document.getElementById('container'),
				mouseSupport	: true,
				limitStickTravel	: true,
			});
			joystick.addEventListener('touchStart', function(){
				console.log('down')
			})
			joystick.addEventListener('touchEnd', function(){
				console.log('up')
			})
			var prevX = 0;
			var prevY = 0;
			var newX = 0;
			var newY = 0;
			setInterval(function(){
				var outputEl	= document.getElementById('result');
				newX = Math.round(joystick.deltaX());
				newY = Math.round(joystick.deltaY()) * -1;
				outputEl.innerHTML	= '<b>Position:</b> '
					+ ' X:'+newX
					+ ' Y:'+newY;
				if ( newX != prevX || newY != prevY ){
				    websocket.send("x:"+newX+",y:"+newY);
				}
				prevX = newX;
				prevY = newY;
			}, 1/30 * 1000);
</script>
</body>
</html>

ESP32のWebサーバーで表示させるHTMLファイルはこんな感じ。エラー処理適当だけどとりあえずESP32のWebSocketサーバーにバーチャルスティックの座標データを送りつける感じ。

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    /*if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
    */
    //Serial.println((char*)data);
    uint8_t ypos = 0;
    char strx[4];
    char stry[4];
    if(data[0] == 'x'){
      for (int i=2; i <= len; i++){
        if(data[i] == 'y'){
          ypos = i + 2;
          break;
        }else{
          strx[i-2] = data[i];
        }
      }
      for (int i=ypos; i <= len; i++){
        stry[i-ypos] = data[i];
      }
      //Serial.println(ypos);
      x = atoi(strx);
      y = atoi(stry);
      #ifdef debug
      Serial.print("x:");
      Serial.print(x);
      Serial.print(",y:");
      Serial.println(y);
      #endif
    }
  }
}

そんでもってESP32側のWebSocketサーバーでバーチャルジョイスティックのデータを受けたらグローバル変数に突っ込んでloopの中でモーターにデータを送る形にしてみた。

HTMLファイルとvirtualjoystick.jsは結構大きくなってしまったのでOnline converter: File to (cpp) gzip byte arrayを使用してgz圧縮してスケッチ内に組み込んでしまった。

とりあえず前に作った車体に乗っけてみて実験してみた。(写真撮りながら難しすぎ

対向二輪タイプの車体なのでバーチャルジョイスティックの値をそれぞれの車輪にどう振り分けるかの計算をもう少し調整しないといけないかも。レスポンス的にも十分問題なさそうなのでもう少し改良を進めようかな。

blynkとかでも同じようなことができるけどESP32だけでスマホ側にアプリのインストール要らないのは便利かもしれない。


2022年1月4日火曜日

Mirakurunにリバースプロキシで外部からアクセスする

 Mirakurunを外部からアクセスするのにApache2のリバースプロキシを使用してみた。これならDigest認証をApache側でやったり、バーチャルホストでサブドメインで管理できたりして便利そうだし。でもMirakurunの作者もリバースプロキシの下に置かないでくださいって言ってるので本当はやってはいけません…ちょっと設定するときだけ使いたかったので…

まずはMirakurun側の設定。Hostnameをちゃんと設定しないとリバースプロキシからアクセスするとJavaScript系が全部403で弾かれてこまった…

sudo EDITOR=nano mirakurun config server

で設定ファイルを開いて、hostnameがもうあるならそちらを編集して、ない場合は追加する。

# logLevel: <number>
logLevel: 2

# path: <string>
path: /var/run/mirakurun.sock

# port: <number>
# You can change this if port conflicted.
# Don't expose this port on the internet, not even with NAPT.
# Use this in LAN or VPN.
# `~` to disable TCP port listening.
port: 40772
hostname: mk.hoge.f5.si

こんな感じでhostnameに使用するDDNSのアドレスを入れて保存する。

sudo mirakurun restart

で再起動する。

次にApache2の設定。

sudo nano /etc/apache2/sites-available/000-default.conf

設定するコンフィグファイルを開いて、バーチャルホストを追加。

<VirtualHost *:80>
ServerName mk.hoge.f5.si

        <IfModule mod_proxy.c>
        ProxyRequests   Off
        ProxyPreserveHost       Off
        ProxyErrorOverride Off
        ProxyPass       /rpc       ws://127.0.0.1:40772/rpc
        ProxyPassReverse        /rpc       ws://127.0.0.1:40772/rpc
        ProxyPass       /       http://127.0.0.1:40772/
        ProxyPassReverse        /       http://127.0.0.1:40772/
        </IfModule>

</VirtualHost>

ここでWebSocketの部分も追加しておかないとメインページしか表示されなくて焦った。認証とかは適宜追加しておく。

sudo a2enmod proxy proxy_wstunnel
sudo service apache2 restart

これで設定が有効になってるはず。

今回ハマったのはMirakurun側のHostnameがちゃんと設定されてないとうまくアクセスできない(mDNSとかでアクセスする際もちゃんと設定しておかないとIPからじゃないと行けないかも)のと、Apache側でWebSocketもリバースプロキシ設定してやらないと行けないところ。/rpcのところは/rpc/だと行けなかったり色々ハマってしまったのでまた設定するとき用のメモでした。

Mirakurunは外部ネットワークからアクセスする想定ではないので本来はVPNとかを使ったほうが良いかも。とりあえず設定が終わったらアクセスできないように設定しておいたほうがよいですな。

2021年12月13日月曜日

RP-PB186を買ってみた。

 最近ぼちぼち電車で出張に行く機会があるのだけれども、iPhone 12 miniだと結構使いまくると帰りのバッテリ残量が怪しい。電子マネーも切符もスマホだからバッテリなくなったらやばいよなぁ。ということでモバイルバッテリを買ってみた。

超久しぶりにモバイルバッテリを購入したわけだけど、とりあえず最近流行りのPD対応でお安くて安心できそうなやつということで、ちょうどRavPowerのRP-186のリニューアル版が安かったので買ってみた。10000mAhで1699円。
Amazonに締め出し食らったようでRavPowerとは言っていない状態だったけど…

パッケージはやっぱりRavPower。品番もPB186っていう表記だったけどね…
ちゃんと両端Type Cのケーブルも付属していた。Amazonの製品画像ではRavPowerロゴ消されていたけど実物にはちゃんと入ってる。
Type Cのモバイルバッテリって充電も放電も同じ端子で行けるのがべんりねぇ。一応Type Aもついてる。Type Aからも9V2A出せるって何の規格なんだろ…
PDは12Vまで出せるとか。
18Wだと思って購入したんだけど、実はiPhoneの20Wに対応してそうな感じ。
USB Type C→Lightningケーブルも買っておけばよかったなぁ。

LEDランプはめっちゃ暗い。だがそれがいい。寝てる間に充電しても眩しくない仕様かな。
充電中はバッテリー残量が点滅する仕様。0から25%だったら一個目のLEDだけ点滅するし、75から99%だったら4個目のLEDだけ点滅する。わかりやすくていい感じ。
充電していないときはボタンを押すとバッテリ残量が確認できる仕様。
4つ目のランプが点滅し始めてしばらくするといきなりコイルが泣き始める…
そしていつの間にか治ってる。まぁほぼ気にならないからいいか。
充電が完了すると全部点灯したままになるっぽい。

まぁセルもLG製とかちゃんと書いてあるし、怪しいメーカーのやつよりはいいかなぁ
やすいし。
12Vまで出力できるからUSB PDデコイとか使って遊べるかなぁ。

2021年12月4日土曜日

Edifier TWS330 NBを買ってみた。

 Aliexpressのセールで安くなったりクーポン配ってたりしていたのでEdifier TWS330 NBを買ってみた。レビューも良さげだった。換算レートが微妙なので3,466円だったけど。
Edifierって昔ドスパラでスピーカーが売っていて結構良さそうだったので聞き覚えがあるメーカーということでこいつにしてみた。一応38dBのアクティブノイキャンがついているのにこの価格は安いし。

今回は箱潰れがひどい。まぁこの箱薄いし…
ちゃんと裏面には日本語も書いてある。まぁAmazonでも売ってるから大丈夫かなということで。
まぁここはあえて白を選択してみたけど、BeatsとAirPods Proの中間ぐらいの丸み。結構丸い。
イヤーピースがたくさんついてる。本体にも装着されているので5セット分。あとはTypeCケーブルが付いてくる。
フィルム剥がしたら静電気でめっちゃゴミが付いてしまったけど、表面はテカテカしてるし質感も良い。
技適マークもちゃんとついてる。蓋の裏側に色々プリントしてあるのはAirPods Proみたいな感じ。
イヤホン本体は結構大きく感じる。
一応アプリからノイキャンと外部取り込みモードが切り替えられる。アプリ自体はBLEで接続してるっぽい。なるほどなぁ

PCとつなぐとアプリが使えないので左のイヤホンをダブルタップでノイキャンと外部取り込みモードとノーマルモードを切り替えられる。
対応コーデックはAACとSBCのみなのでiPhoneやMacなどのApple製品系ではAACで使用できるけどWindows 10だとSBCになってしまう。Windows 11ではAAC対応したみたいなんだけどなぁ。
試しにMacBook ProにつないでみたらちゃんとAACで接続されていた。

ちなみにイヤホン本体は1時間の充電で4時間(ノイキャンオフで5時間)持つらしい。ケースは1.5時間の充電で3回イヤホンを充電できるらしい。
意外と充電早いなぁ。

この価格でANCついてて、ちゃんとしたメーカー製で技適も付いてるし、TWS入門機としてはなかなかいいかもしれない。


2021年11月17日水曜日

TOKAIZ TWA-001をLinuxで使ってみた。

  OrangePi PC用に11acのUSB WiFiアダプタを探していたらTOKAIZのTWA-001が安かったのでなんのチップが載っているのか調べてみた。技適にも対応してるみたいだし。Linuxで使う場合はチップがわかっていればドライバモジュールを探して使えたりするので。
しかしTOKAIZのTWA-001、日本向けっぽくて情報がない。ということで技適の番号210-138110で調べてみるとどうやら中身はCOMFASTのCF-811ACだということが判明。形状は全く同じで刻印が違う感じ。そんでもってCF-811ACはRealtekのRTL8811CUが乗ってるらしい。

そしてドライバモジュールも存在することがわかった。
Realtek RTL8811CU/RTL8821CU USB Wi-Fi adapter driver for Linux

この手のドングル、見た目は同じっぽいけど別なチップが乗っていたりするので難しい。たとえばTP-LinkのArcher T2U Nanoも同じぐらいの価格で、同じようなサイズだけど、RealtekのRTL8812AUが載っていて、RTL8811CUとは品番が似てるけどドライバモジュールに互換性がなかったり。

RTL8811AUとRTL8812CUの違いはちょっと調べたぐらいでは公開されているドライバモジュールではMonitor Modeの対応の違いがありそう。RTL8812AUだとaircrack-ngから出ているドライバモジュールでMonitor modeでairmon-ngが使える。RTL8811CUだとiwしか使えない。(追記:Monitor mode使えるらしいです。コメントいただきました。)
というぐらいしか見つからなかったので特殊な使い方をしなければどちらでも良さそう…

どちらもhostapdは使えそうなので、APモードも問題なさそう。

とりあえずTP-LinkのArcher T2U NanoよりTOKAIZのTWA-001が安かったので早速ポチってみた。

USBに接続すると出っ張り具合はこんな感じ。10cmぐらいのフレキシブル延長ケーブルでOrange Pi PCから離すと電波強度が良くなる。

早速ドライバモジュールをビルドしようと思ったんだけど、試しにifconfigしてみたら普通に認識していた…
lsmodするとちゃんと8821cu(rtl8821cuとrtl8811cuはドライバが共通っぽい)が読み込まれていた。
ちなみにOSはArmbian 21.08 bullseye
インターフェース名がwlxのあとにmacアドレスになっててすごく覚えにくいけど。
Armbianのフォーラムを見る限り、上で貼ったドライバモジュールがマージされてそうな感じだった。sudo iw listをしてみるとAPモードも使えそう。
"interface combinations are not supported"となっているので2.4GHzと5GHzは同時使用できないっぽい。

しかもArmbianにはhostapdも最初から入っていたので設定ファイルさえ作ればAPモードでもすぐに使用可能だった。とりあえず簡易的な設定でAPモードにして繋いでみたら普通につながったのでapmodeも対応しているモジュールが入っているっぽい。brctlも入ってて至れり尽くせり。

hostapdで11ac(5Ghz)で飛ばして1mぐらいの距離でiPhoneからスピードテストしてみたところ上下95Mbpsぐらい出たんだけど、Orange Pi PCのLANが100Mbpsなのでそこがボトルネックになってる。11n(2.4Ghz)だと35~40Mbpsだった。

11acもビルド無しでデフォルトで使えるのは便利だな~
Orange Piシリーズだと技適の問題とかがあるのでどうせ内部接続USBのWiFiを積むならOrange Pi PCみたいにWiFi無しタイプも取り扱ってほしいな。(もしくはヒートガンで剥がすしか無いのか…
Armbianでそのまま11ac使えるようにしてくれてるし。

あとから気がついたんだけどEDUPのEP-AC1689だと技適対応(レビューに技適シール写ってた)でUSB3.0で1300Mbps(5GHz:867Mbps+2.4Ghz:400Mbps)で値段も100円しか違わなかった…
ちなみにEP-AC1689に乗ってるICはRTL8812BUで、Linux用のドライバモジュールもあるみたい。
USB2.0でつかったときの速度はどうなるかわからないけど…(理論上の帯域は足りてないけど。

Realtek RTL8812BU/RTL8822BU USB Wi-Fi adapter driver for Linux
ちなみにArmbianの/lib/modulesの中を覗いてみたらこれも対応してそう。
(rtl88x2buとrtl8812auとrtl8811cuがもうすでに入っていた。)

しかし11acなRealtekのICは種類がおおいな…そして性能的な違いがよくわからない…(ドライバモジュールが対応してないだけで機能的にはついていたり)

ちなみにTWA-001をRaspberry Pi 3Bに繋いでみたけどRaspberry Pi OSにはドライバモジュールが入っていないようだった。でもラズパイ向けの情報はたくさんあるのでビルドすれば普通に使えそうな気がしている。

追記:カーネルモジュールをビルドしたらRaspberry Pi 3BでTWA-001を使用できました。

2021年11月14日日曜日

Orange Pi PCでRTC-8564NBを使ってみた

 Armbianを入れたOrange Pi PCにi2c接続のRTCをつけてオフラインでも時間を保持するようにしてみた。今回も秋月のRTC-8564NBモジュールを使用してみた。Raspberry Pi OSにもともと入っているモジュールでpcf8563として使えたのでArmbianでもそのまま使えるんじゃないかな…

まずはsudo armbian-configで設定画面を開いて
System→Hardwareでi2c0を有効にしておく。
sudo i2cdetect -y 0
でRTCが認識されてるかどうか確認した。

ラズパイの場合はDevicetree Overlayで簡単にRTCの設定ができたので、Orange Pi PCのArmbian上ではどうなんだろうと思って調べてみたら自分でDTSファイルを作成してuser_overlayで読み込ませればデバイスツリーでもRTCが使えるみたい。

Device Tree overlays for sunxi devicesにds1307を使用したサンプルがあったのでRTC-8564NB用に少し変更して使ってみた。i2cバスのレジスタさえ合わせれば他のArmbianなデバイスでも同じように設定できるかも。

/dts-v1/;
/plugin/;

/ {
	compatible = "allwinner,sun4i-a10", "allwinner,sun7i-a20", "allwinner,sun8i-h3", "allwinner,sun50i-a64", "allwinner,sun50i-h5";

	fragment@0 {
		target-path = "/aliases";
		__overlay__ {
			rtc0 = "/soc/i2c@01c2ac00/pcf8563@51";
		};
	};

	fragment@1 {
		target = <&i2c0>;
		__overlay__ {
			#address-cells = <1>;
			#size-cells = <0>;
			pcf8563@51 {
				compatible = "nxp,pcf8563";
				reg = <0x51>;
				status = "okay";
			};
		};
	};
};

これをpcf8563.dtsとかにして保存したあとに

sudo armbian-add-overlay pcf8563.dts

を実行すると自動でコンパイルされて、/boot/armbianEnv.txtに追記してコンパイル済みのdtboが読み込まれるようにしてくれる。
あとは再起動してtimedatectlを実行してみるとRTCが認識されてるはずなので

ls /dev
してみる。

するとAllwinner H3自体にRTCが内蔵されているため、Orange Pi PCではrtc0とrtc1としてRTCが2つ認識されてしまった。どっちがRTC-8564NBなのかは時間を読み込んで見ればわかるので
timedatectl
を実行してみる。そうするとrtc0はCPU内蔵のRTCで、rtc1がRTC-8564NBになっていた。

rtc0が優先されるっぽいのでrtc1がある場合にはrtcへのシンボリックリンクをrtc1にするスクリプトを起動時に実行されるようにする。

sudo nano /etc/rc.local
でexit0の前辺りに

if [ -e /etc/rtc1 ]; then
  ln -f -s /dev/rtc1 /dev/rtc
  hwclock -f /dev/rtc1 -s
fi

と追記して保存。これで起動時にrtc1がデバイスツリーで追加された場合はrtcへのシンボリックリンクをrtc1に変更した後にrtc1の時刻を読み込んでくれる。

今回は時計バックアップ用バッテリに秋月で売っていたMSリチウム二次電池のMS621FEをバックアップ電源に使ってみた。3.3Vからダイオードと抵抗だけで充電できるし良いんじゃないかなと。一緒に低Vfのダイオード買っておけばよかった…最初電池で行こうとしていたので低リークな1SS380しか買ってなかったので、とりあえずこれと1kオーム入れてみたら2.88Vぐらいまでしかチャージされなかった。
秋月だと1SS154あたりが低Vfっぽいので良さそうなので次回買ってみようかな。用途違うけど…