Wio TerminalでBad Apple!!の影絵を再生してみた

Wio Terminalが届いたので、例の影絵を再生してみました。


Wio TerminalでBad Apple

ひとまず描画データはPCからUSB UARTで送信しており、音声は別再生です。
白黒2値限定ですが、差分だけを描画するという方法で、一部の前フレームとの差分が極端に大きいシーンを除いて320px × 240pxで30fps出ることが確認できました。
(とはいえほぼこの影絵専用なのであんまり応用が効かないという難点が…)

Get Started with Wio Terminal - Seeedウィキ(日本語版) に従って、Arduinoで開発していますが、
Wio Terminal側の再生用のソースコード(↓)はわずか32行で、非常に手軽に開発ができるようになっているのが良いですね。

#include"TFT_eSPI.h"
TFT_eSPI tft;

void setup() {
  tft.begin();
  tft.setRotation(1);
  digitalWrite(LCD_BACKLIGHT, HIGH);
  tft.fillScreen(TFT_BLACK);
}

uint16_t read_word() {  // シリアルからwordデータ受信
  while (!Serial.available());
  uint16_t x = Serial.read();
  while (!Serial.available());
  return (x << 8) | (uint16_t)Serial.read();
}

void loop() {
  static uint32_t col = TFT_WHITE;  // 白黒交互に描画する
  uint32_t offset = 0;
  int cnt = read_word();  // 差分描画データ数
  while (cnt--) {
    uint32_t start = offset + read_word();  // 差分描画開始位置
    if (start == 0xffff) {
      offset = 0xffff;
      start = offset + read_word();
    }
    uint16_t len = read_word();  // 連続して描画するピクセル数
    uint32_t finish = start + len;
    for (uint32_t j = start; j < finish; j = j - j % 320 + 320) {
      tft.drawFastHLine(j % 320, j / 320, finish - j, col);  // drawFastHLineで横1ライン分を描画
    }
  }

  col = col == TFT_WHITE ? TFT_BLACK : TFT_WHITE;
}


なおデータは以下のように作っています。

続きを読む

Arduino ESP32からBluetoothシリアル(SPP)デバイスに接続する

Arduino ESP32は、Bluetoothシリアル(SPP)のスレーブデバイスになるサンプルは付属しており、PCからはBluetoothシリアルで容易に接続することができました。
(例えば arduino-esp32/SerialToSerialBT.ino at master · espressif/arduino-esp32 · GitHub など。)

少し前に、Bluetoothシリアルのマスター側になり、他のデバイスに接続するためのライブラリやサンプルも用意されたようです。
arduino-esp32/SerialToSerialBTM.ino at master · espressif/arduino-esp32 · GitHub

これを使って、ESP32からBluetooth対応のUSB電圧/電流センサーに接続して情報を取得してみました。使ったUSBセンサーはこれ。

UM25C

arduino-esp32 は、Arduino IDEのボードマネージャから1.0.4にアップデートしたものを使っています。

ESP32からデバイスBluetooth SPPで接続する

まずは、setupでデバイスに接続を行ってみます。
接続は、Bluetoothバイスの名前、もしくはアドレスを直接指定して行います。
アドレスの方が高速に接続できるようです。

#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

String name = "UM25C";
uint8_t address[6]  = {0x00, 0x11, 0x55, 0x33, 0x22, 0x77};  // アドレス直接指定も可能
char *pin = "1234"; //<- 接続用PIN。標準以外の場合は指定が必要

void setup() {
  Serial.begin(115200);

  // 初期化。名前とtrueを引数として渡すとマスターになる
  SerialBT.begin("ESP32test", true); 

  Serial.println(String("Connecting to ") + name + " ...");
  // 標準以外のPINが必要な場合はここで指定する
  // SerialBT.setPin(pin);

  // 名前を指定して接続する場合。スキャンのためか、10〜15秒程度かかる
  bool connected = SerialBT.connect(name);
  // アドレスを指定して接続する場合。こちらは2〜3秒で高速に接続できる
  //bool connected = SerialBT.connect(address);
  
  if (connected) {
    Serial.println("Connected Succesfully!");
  } else {
    while (!SerialBT.connected(10000)) {
      Serial.println("Failed to connect. retry ...");
    }
  }
}

Bluetooth SPPデバイスと通信する

このUSBセンサーは、0xf0 という 1 byte のコマンドを送ると、130バイトほどのバイナリデータを返してくるようになっています。
1秒に1回 ESP32 からコマンドを送り、受け取ったデータをシリアルでPCに返してみます。

byte buffer[130];
int pos = 0;

void loop() {
  static unsigned long last_wr = 0;
  if (millis() - last_wr > 1000) {
    SerialBT.write(0xf0);
    last_wr = millis();
  }
  while (SerialBT.available()) {
    buffer[pos++] = SerialBT.read();
    if (pos >= sizeof(buffer)) {
      handle_data();
      pos = 0;
    }
  }
  delay(20);
}

void handle_data() {
  String data = 
    String("{\"V\":") + String((buffer[2] * 256 + buffer[3]) / 1000.0, 3) +
    ",\"A\":" + String((buffer[4] * 256 + buffer[5]) / 10000.0, 4) +
    ",\"W\":" + String((buffer[8] * 256 + buffer[9]) / 1000.0, 3) +
    ",\"C\":" + String(buffer[10] * 256 + buffer[11]) +
    ",\"mAh\":" + String(buffer[16] * 65536L + buffer[17] * 4096L + buffer[18] * 256 + buffer[19]) +
    ",\"mWh\":" + String(buffer[20] * 65536L + buffer[21] * 4096L + buffer[22] * 256 + buffer[23]) +
    ",\"ohm\":" + String((buffer[124] * 256 + buffer[125]) / 10.0, 1) +
    "}\n";
  Serial.print(data);
}

これで、シリアルモニターにUSBセンサーの値がJSONっぽい形式で返ってきました。

{"V":4.697,"A":1.2048,"W":5.658,"C":19,"mAh":646,"mWh":3049,"ohm":3.8}
{"V":4.700,"A":1.3035,"W":6.126,"C":19,"mAh":647,"mWh":3051,"ohm":3.6}

現状の問題点

どういうわけか、デバイスと接続できなくても20〜30秒程度経過すると connected = true が返ってきてしまいました。
また、途中でデバイス側のリセットなどで切断された場合、仮に SerialBT.disconnect(); SerialBT.connect(); を実行し直しても再接続がうまくいきません。
仕方なく、一定時間応答がない場合、 ESP.restart() でESP32全体を再起動するという処理を入れると、うまく再接続されています。

LinuxのHDDに不良セクタが発生したので修復してみた

NAS(Linux機)からファイルを読み出していたら、突然ハングアップしてしまい‥…dmesgを調べてみるとHDDに不良セクターと思しきI/O Errorが発生していました。

sda: Current: sense key=0x3
    ASC=0x0 ASCQ=0x0
Info fld=0x43c024d6
end_request: I/O error, dev sda, sector 1136665808
Buffer I/O error on device sda, logical block 142083226

確かに、このセクターをddで読み出してみると、延々カシャカシャ音を立てるばかり。しばらくするとI/O Errorで失敗します。

dd if=/dev/sda of=/dev/null skip=$((1136665808/8)) bs=4k count=1
(しばらく固まる)
dd: /dev/sda: Input/output error


こういう時は、このセクターに書き込みを行うと、HDDのファームウェア代替(だいたい)セクターに置き換えてくれるはず。
このセクターはファイルデータの一部であることが分かっているので、とりあえず0で埋めます。(ファイルシステムメタデータの一部だと厄介。と言っても0で埋めてあとでfsckをかけてみるくらいしか無いのですが)

注意点として、セクターは512Byte単位ですが、書き込みはLinuxのページサイズである4KB単位で行わないと、データをいったん読み出してから書き込もうとするので、またI/O Errorになってしまいます。
したがって、以下のようにbs=4kを指定して、セクタ番号を8で除算した値を指定します。間違うと別のデータを破壊してしまうので注意。

sudo dd if=/dev/zero of=/dev/sda seek=$((1136665808/8)) bs=4k count=1
1+0 records in
1+0 records out

書き込みに成功しました。

もう一度読んでみると

dd if=/dev/sda of=/dev/null skip=$((1136665808/8)) bs=4k count=1
1+0 records in
1+0 records out

正常に読み出せるようになっていました(というか、そう見えるように代替セクターに置き換えられていると思われます)。


前後のセクターを読んでみても正常そうなので、しばらくこれで様子を見てみます。

ESP32をBluetoothキーボードやマウスにする

Arduino Leonardo等は、USBキーボードやマウスとして振舞わせ、PC等を操作するのに使うことができます。
同じくArduino IDEをプログラミング環境として使えるESP32ではUSBキーボード機能は付いていません。しかし、代わりにBluetooth LEを利用してキーボードとして振舞わせることができるはず。ということで、やってみました。

セットアップ

今回はM5StickCを使いましたが、ESP32ならどのボードでも同様です。

M5StickC ESP32 PICOミニIoT開発ボードフィンガーコンピューターカラーLCD付き

Arduino IDEでESP32を使用するためのセットアップ手順は、下記サイトなどを参考にしてください。
esp32_setup – スイッチサイエンス

BLE HID (Human Interface Device)のためのクラスはESP32のツールキットにすでに含まれていますが、キーボードやマウスとして振る舞うのに必要なセットアップをクラスにまとめたライブラリを作ってくれている方がいるので、これを利用すると簡単です。

  • キーボード

github.com

  • マウス

github.com

このライブラリをダウンロードして、スケッチを保存している Arduino フォルダの下の libraries 内に入れて IDE を起動すれば利用できるようになります。


ライブラリの使い方は、ファイル>スケッチ例>(ライブラリ名) 以下のサンプルを見れば一目瞭然なほど簡単です。
例えば以下のスケッチは、5秒ごとに Hello world とタイプし、エンターキーを押すものです。

#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup() {
  Serial.begin(115200);
  bleKeyboard.begin();
}

void loop() {
  if (bleKeyboard.isConnected()) {
    bleKeyboard.print("Hello world");

    bleKeyboard.write(KEY_RETURN);
  }
  delay(5000);
}

これをESPに書き込んで起動して、BLEに対応したPCやスマートフォン等からBluetoothバイス一覧を開くと、 「ESP32 BLE Keyboard」というデバイスが見えます。これに接続してテキストエディタ等に切り替えると、5秒ごとに入力されるのが確認できます。
PCだけでなく、iPhoneでも動作を確認できました。

マウスでも同様です。ただし、こちらはiPhoneでは動作が確認できませんでした(Assosiative Touchを有効にしてみたりしたのですが、ボタンもカーソル移動も反応せず…)。

追記

コメントで教えていただきました。多少の修正で接続できるようになるとのことです。
参考:
IoS 13 compatibility? · Issue #3 · T-vK/ESP32-BLE-Mouse · GitHub
ESP32でiOSにも繋がるBLE Mouse - Qiita


このままだと勝手に動くだけですが、せっかくM5StickCを使っているので、カウントダウンを表示したりボタン操作で一時停止などができるようにしてみました。
下記サンプルではCtrlキーを5秒ごとに押しています。(これだけだとあまり意味はないですが、ゲーム等と組み合わせたり、使い方は工夫次第ということで。)

続きを読む

太陽電池で動くワイヤレス環境センサー

前回の記事のドアセンサーで使ったTWELITEを使って、太陽電池で動くワイヤレス環境センサーを作りました。
BME280で温度・湿度・気圧を計測し、定期的に無線で送信することができます。電気二重層コンデンサを搭載しており、夜間もデータ送信が可能です。
主にエナジーハーベストの実験目的です。


使ったもの

受信用はMONOSTICKをRaspberry PiにUSB接続しています。

太陽電池はTWE-EH SOLARに付属していますが、試しに室内用の別のパネルに変えてみました。日の入らない室内など、暗めの環境では有効ですが、屋外など十分昼に明るくなる環境であれば付属のものでも十分動作しそうです。

※ BME280のブレークアウトボードは、商品名にSPIとか書いてありますがI2C接続です。

作り方

配線方法は、TWE-EH SOLARの製品ページにあるPDFの通りです。
https://mono-wireless.com/jp/products/TWE-EH-SOLAR/index.html

裏面はこんな感じに。だいぶ空間が空いていてスカスカ。ケースは手持ちの適当なやつですが、ちゃんと選べばもっと小さくもできそう。

TWELITEには上記ページにある「無線タグアプリ」の子機用のファームウェアを書き込み、10分に一度,BME280の情報を送信するように設定します。注意書きにある通り、起動時のOTAは無効にするようオプションビットを設定しておきます。

またMONOSTICKにも「無線タグアプリ」の親機用(MONOSTICK BLUE用)を書き込みます。

すると、親機の方のシリアルにデータが流れてくるので、あとはこれを記録してあげればOKです。

キャパシタ電圧の変化

A1の値として、キャパシタ(電気二重層コンデンサ)に蓄電されている電圧値(の1/2の値)を取得することができます。これをプロットしてみると、夜間に暗くなった時にキャパシタの電力を使って動作している様子を見ることができます。以下は約4日分のデータ。夜間に電圧が落ちて,日が出てくると一気に充電されています。

f:id:NeoCat:20190804112422p:plain

今回設置したのは屋外の直射日光の当たらない場所ですが、ちゃんと終夜で動いています。ただし、夜間も暗いながらも夜間灯の光が入っており、多少は消費を緩和している可能性はあります。

ボタン電池で年単位で使えるワイヤレスドアセンサーを作った

ボタン電池で数年ほど動作し続ける、ワイヤレスなドアセンサーを作りました。よくあるIoTネタですが、オフィスのトイレ個室が混雑しがちなので、自席で空きがわかると良いなというやつです。

外観と、ドアの上部に載せたところ。
f:id:NeoCat:20190507122108j:plain

仕様

センサーは、1分間ごとにドアの開閉状態を無線送信します。(リアルタイム送信もできるのですが、あえて1分間ごとのみにしています。用途的に、混雑しているかが荒く分かればよく、逆にあんまりリアルタイムに監視したくないからという理由です。)
複数のセンサー情報を集約して混雑しているか空いているかをWebページで見ることができます。

使用したもの

無線送信にはTWE-LITEを使用しました。簡単に省電力な無線システムを作ることができます。いくつかタイプがありますが、ボタン電池ホルダーが付いているPALを使いました。

無線マイコンモジュール TWELITE BLUE PAL-トワイライト ブルー パル MW-B-PAL-P 【アンテナ内蔵タイプ 標準出力(1mW級)】

実は 開閉センサーPAL|MONO-WIRELESS.COM というのが販売されているので、これを使えばあとは磁石さえ用意すればOKです。
が、そこそこ値段もするので、今回はセンサーにはリードスイッチをつけて使います。

ガラス管の金属リードがわずかに離れて2本入っており、両側のそれぞれに磁石のS極とN極を近づけると、磁力でくっついて導通するという仕組みです。

Hommy 磁気センサー 10xMKA-10110 10-15AT磁気センサー N/O SPSTコンタクトリードスイッチ

子機のケースにはタカチのSW-90Bを使用。ちょっと長さも高さも余ってしまいますが、幅の小さいケースがなく……。
SW型プラスチックケース 黒 SW-90Bの通販ならマルツオンライン

マグネットはこれ。めちゃくちゃ小さいので対向位置にくっついていても邪魔になりません。ただし位置は結構ぴったりと合わせる必要があります。

OMO Magnetics 【世界最強マグネット】 強力マグネット 強力磁石 N50 ネオジム磁石 ネオジウム磁石 円柱型 丸型 2x10mm 直径2mm 厚み10mm ニッケルメッキ 専用ケース付 20個セット

親機としてはDIPタイプのTWE-LITEを使いました。

超簡単! 無線マイコンTWELITE DIP-トワイライトディップ 2次元マッチ棒アンテナタイプ ピンヘッダ端子実装

親機からWi-Fi経由でデータを流すのにはM5Stick-Cを使いました。
M5Stick-C - スイッチサイエンス

配線

こんな感じにリードスイッチをつけます。
f:id:NeoCat:20190605101954j:plain

A1 --+-- 1〜5MΩ  -- Vcc
     |
  リードスイッチ
     |
GND -+

リードスイッチがONになると、A1がプルダウンされます。

親機側は、M5Stick-Cにくっつけました。ちょっと強引
f:id:NeoCat:20190430204116j:plain

ファームウェアの書き込みと設定

ファームウェアとして、以下のページからダウンロードできる「無線タグアプリ」を使用しました。
無線タグアプリ (App_Tag) - MONO-WIRELESS.COM

子機には子機用のバイナリ(現時点で最新のv2.1.5)を書き込みます。適当な3.3VのFTDI USBシリアルでGND/TXD<->RXD/3.3Vにつなぎ、PRGをLOWに落として電源を入れ、PRGをHIGHに戻すとプログラムモードに入ります。
接続方法は以下。 UART-USBケーブルでの書き込み - MONO-WIRELESS.COM
Mac/Linuxから書き込む際は、SDK(ソフトウエア開発環境 TWELITE SDK - MONO-WIRELESS.COM)の Tools/tweprog_py に含まれるPythonのツールを使います。

sudo pip3 install pyftdi
sudo ./tweterm.py -p ftdi:///1 -F ./App_Tag-EndDevice-BLUE.bin  # ファームウェアを指定して書き込み

M2ピンをLOWにして起動すると設定画面に入り、以下のように表示されます。

--- CONFIG/App_Tag V2-01-5/SID=0x810e____/LID=0x01/RC=9065/ST=0 ---
 a: set Application ID (0x810e____)
 i: set Device ID (1=0x01) 
 c: set Channels (20) 
 x: set Tx Power (13) 
 b: set UART baud (38400) 
 B: set UART option (8N1) 
 k: set Enc Key (0x5A5A5A5A) 
 o: set Option Bits (0x00000441)*
 d: set Sleep Dur (60000)*
 w: set Sensor Wait Dur (30) 
 m: set Sensor Mode (0x10) 
 p: set Sensor Parameter (0)*
 P: set Sensor Parameter2 (  ) 
---
 S: save Configuration
 R: reset to Defaults

ここで、App IDやチャンネルなどを適当に設定していきます。子機が複数あるので、IDとして1, 2, 3などと違う番号をつけておきます。 オプション(o)として 0x441 を指定して、OTAは無効にしておきます。センサータイプは10(アナログセンサー)にしておきます。

親機には親機用のバイナリを書き込みます。こちらは書き込み後に + + + と入力すると設定用のインタラクティブモードに入るので、子機と同じApp ID、チャンネルを指定します。
無線受信したデータがシリアル出力されるので、これをESP32側のSerial1で受け取り、適当にパースしてWi-Fi経由でHTTP送信するようにしました。

結果

こんな感じで混雑状況がわかります。
あえて個別の開閉状態は表示しないようになっています。

空いた時にお知らせしてほしいとはみんな思うようで、早速 1liner でこのページを定期取得して空きができたらポップアップ通知するスクリプトを書いた人などが現れていますw


ちなみに親機と子機は壁2枚隔てた距離にありますが、かなり感度は良好で滅多にデータ欠損などはないようです。

EXAPUNKSのソリティアを自動操作で解く

前回の記事ソリティアの解法を求めるコードを紹介しましたが、これに画像認識と自動マウス操作機能を加えて、全自動で解くようにしました。

youtu.be

コードはGitHubにあります。 GitHub - NeoCat/exapunks_solitaire_solver: Automate EXAPUNKS ПАСЬЯНС (Solitaire)

画像認識は、screencaptureコマンドで撮ったスクリーンショットOpenCVで画像認識しています。画像認識といっても、ゲーム画面の位置を探し出して切り抜き、カードの左上の部分のテンプレート画像を用意しておいて match_template でマッチングしているだけの簡単なものです。

これで認識したカードの並びを前回のソルバーで解き、その手順に従ったカード移動操作を cliclick で自動化しています。