Wireless Analog Terminal Bellを作った

Wireless Analog Terminal Bell

Analog Terminal Bell の動画にInspireされて、Wireless Analog Terminal Bell を作りました。

ターミナルで作業している時に何か間違えると、分かりやすく(物理的な)ベルが鳴ってお知らせしてくれます。

youtu.be

仕組み

端末へ出力されるのをPTYを中継するrubyプログラムが監視しており、ベル文字 "\a" ( "\x07" )が出てくるとHTTPリクエストを送信します。
これを受けて、M5Stick-C (バッテリー入りESP32として使っています) がソレノイドを動かし、ベルのハンマーを叩くことで音が鳴ります。

作り方

使ったもの

卓上ベル
小型ソレノイド 5V ROB-11015
MOSFET IRLU3410PBF
M5StickC

他にダイオードと適当な抵抗(1k〜10kΩくらい?)、安定動作のための大きめの電解コンデンサが必要です。

配線

こんな感じで配線します。(見にくいですが何となく察してください)

   M5Stick-C       FET  Diode   
 +-- BATT ---------------+-- Solenoid
 |   G26  -- 1kΩ -- G    ⊼     |
470uF               D ---+-----+
 +-- GND  --------- S

ソレノイドをうまくハンマーを押し出せる位置に、両面テープで固定します。
M5Stick-Cと回路はハンマーの邪魔にならないよう、ベルの台座の下にうまく納めます。

通電は一瞬*1とは言え、ソレノイドは結構な電流を消費するので、M5Stick-Cは十分充電しておく必要があります。

コード

使用したコードは以下にあります。

Wireless Analog Terminal Bell using M5Stick-C · GitHub

SSID等を設定してM5Stick-C にプログラムを書き込み、シリアルモニタでIPアドレスを確認します。
そして、pty-proxy.rb にそのアドレスを書き込み、Terminal上で起動します。

あとはベル文字を printf \\a 等で出力すれば、ベルが鳴ってくれるはず!

pty-proxy.rb について

pty-proxy.rb はPTYへの出力を拾ってそこからBELL文字を見つけたら削除した上でHTTPリクエストを送ります。
しかし、BELL文字だからとなんでも削除して良いわけではなく、エスケープシーケンスの一部を削除してしまうと無駄にベルがなったり端末の表示が崩れるかもしれません。
特に、OSC Mode というウィンドウタイトルなどを制御するエスケープシーケンスは終端に BELL文字を使っています。
そこで、エスケープシーケンス中かどうかを判定し、そうでない場合のみベルを鳴らすようにしています。

*1:私は50ms程度の通電でうまく鳴りました

Bluetooth対応のHue(スマートライト)をRubyとBLEで制御してみた

Bluetooth対応のHue(スマートライト)をRubyとBLEで制御してみた

去年くらいに、PhilipsからBluetooth LEに対応したHue (スマート電球) が発売されました。
HueはもともとZigbeeを使用しており、色や明るさの制御にはHueブリッジという装置をLANに接続する必要があったのですが、Bluetooth LEに対応したことでブリッジがなくともスマートフォンの専用アプリから操作することができるようになり、導入がしやすくなったということです。


Philips Hue フルカラー シングルランプ Bluetooth + Zigbee対応|E26 LED電球 スマートライト


これを、スマートフォンアプリを介さずにRaspberry Pi等から制御できないか試してみました。

結論から言えば操作は可能でした。

ただし、古いバージョンのbluezだとペアリングがうまくいかなかったり、スマートフォンアプリからファクトリーリセットを行わないとペアリングができなかったりするなど、ハマりどころが結構多く、やや面倒だなという感じです。
また、PhilipsはBLEでの制御インターフェースは正式サポートしているわけではないので今後アップデートで変わる可能性もあるかも、としているようで、素直にブリッジを導入してそのHue APIを使うのが無難ではあります。

やり方

先に、制御する側のPCやRaspberry Pi等に最新版のbluezを導入し、Bluetooth LEが使えるようにしておいてください。
5.50-1.2~deb10u1 というバージョンでは動作を確認できましたが、これより古い物では動作しませんでした。

sudo apt upgrade bluez
sudo systemctl start bluetooth
sudo hcitool lescan 

を実行して、ずらずらとBLEデバイスが検出されればOKです。うまくいかない場合、

sudo hciconfig hci0 down
sudo hciconfig hci0 up

BluetoothアダプタをOFF/ONしてみるとうまく動作することがあります。


制御用のスクリプトRuby gemにしてあるので、まずはgemをinstallします。(gemのコードはこちらです → https://github.com/NeoCat/ruby_hue_ble )
まず適当なディレクトリに Gemfile を作って以下のように記述します。
ble gemに依存していますが、これが割と古くて現在のbluezのインターフェースと変更があるので、パッチを当てたバージョンを指定する必要があります。(Merge Requet取り込んでくれないかなあ)

source 'https://rubygems.org'

gem 'hue_ble'

# ruby_ble-1.0.0 is not compatible with recent bluez DBus interface, so use patched version...
gem 'ble', git: 'https://gitlab.com/NeoCat/ruby-ble.git'

そしてgemをインストールします。

gem install bundler # bundler が入っていない場合
bundle install --path vendor/bundle

bundle exec irb を起動して試すのが簡単です。
まずライブラリを require し、Hueデバイスをスキャンします。
スキャンの前に、以下のペアリングが可能な条件を満たすようにする必要があります。

  • スマートフォンアプリ "Phillips Hue Bluetooth" を使って電球を登録したあと、「リセット」を実行する
  • リセットから時間が経っている場合、電球の電源をOFF/ONし直す
  • RasPi等のBLEアダプターと電球を十分に(<90cm)接近させる

ペアリングは一台のホストとしかできませんので、 "Hue Bluetooth"アプリとRasPi両方から制御するといったことはできません。

> require 'hue_ble'
=> true
> HueBLE::scan_cli
Scanning devices ...
Found 1 Hue devices:
  C1:23:45:67:89:AB  Hue Lamp
Found a new Hue Lamp  E2:34:56:78:9A:BC : Do you want to pair? (Y/n)> y

ペアリング済みのHue電球の一覧が(あれば)表示されます。
新規に見つかったHue電球がある場合、ペアリングするか聞いてきます。
なお、このアドレスはファクトリーリセットするとランダムに変わります。

ここで y を選択するとペアリングを試み、上記の条件が満たされていれば成功するはずです。

うまくいかない場合、

sudo hciconfig hci0 down
sudo hciconfig hci0 up

で改善されることもあります。
(しかし systemctl restart bluetooth するとすでにペアリングしていたデバイスが消えてしまいます…。こうなるとまた電球をスマートフォンアプリからリセットしてやり直すしかありません。
リセット時は HueBLE::scan_cli(true) とすると、全デバイスがbluezから一旦削除されるので、これでやり直しができます。)


スキャンが成功すると、 HueBLE クラス内にHue電球の一覧が登録され、 HueBLE.hues でハッシュとして取得できるようになります。(keys = BLEアドレス、values = デバイス制御用のオブジェクト)

登録されたら、

HueBLE.hues.each_value { |hue| hue.on }
HueBLE.hues.each_value { |hue| hue.off }

で全部まとめてON/OFFしたり、色(色温度またはフルカラー電球の場合は色)や明るさを取得したり制御したりできます。

> hue = HueBLE.hues['C1:23:45:67:89:AB']
> hue.brightness  # 明るさ: 1 - 254
254
> hue.brightness = 200
> hue.color_temperature = 100  # 色温度: 1 (白) - 511 (黄)
> hue.color = [16000, 1]  # 色: x, y. それぞれ 1 - 65534 , 範囲外だとエラーとなる。この例は紫色

色は、CIE XYZのx, yを、1〜65534までのいずれかの値に変換した数値で指定するようです。色空間の範囲外だとエラーが返ってきます。
CIE 1931 色空間 - Wikipedia

party!

# 全電球をカラフルにランダム変化させる
def party!
  30.times do
    HueBLE.hues.each_value do |hue|
      hue.brightness = rand(253) + 1
      hue.color_temperature = rand(510) + 1
      10.times do
        hue.color = [rand(65533) + 1, rand(65533) + 1]
        break
      rescue BLE::Characteristic::NotFound
        break
      rescue
        next
      end
    end
    sleep 1
  end
end

party!


とかやって楽しみましょう。

ESP32 (M5Stick-C) で電波時計を合わせよう

ふと気がついたら自宅の電波時計がかなり進んでいました。そこで、ESP32 (M5Stick-C)を使って擬似的に微弱なJJYの標準電波を発信するデバイスを作りました。

M5StickC


基本的には下記のコードをベースにしたものですが、ESP32のArduinoライブラリはNTP同期機能を含んでいるので、より簡単になっています。

neocat.hatenablog.com


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


さて、JJYは40kHzまたは60kHzの信号ですが、これはLEDCをGPIOにアタッチし、 ledcWriteTone() で出力できます。

#define GPIO_PIN 10             /* JJY擬似信号を出力するピン. 10は内蔵の赤色LED */
#define LEDC_BASE_FREQ 60000.0  /* JJY擬似信号の周波数 */
#define LEDC_CHANNEL_0 0

  // 出力ON
  pinMode(GPIO_PIN, OUTPUT);
  ledcAttachPin(GPIO_PIN, LEDC_CHANNEL_0);
  ledcWriteTone(LEDC_CHANNEL_0, LEDC_BASE_FREQ);

  // 出力OFF
  ledcDetachPin(GPIO_PIN);
  pinMode(GPIO_PIN, INPUT);  /* ハイインピーダンスにする場合 */

簡単ですね。あとは、JJYの信号パターンに従って、ON/OFFさせればOK。
完成版のコードは本記事の末尾にあります。

ハードウェアとしては上記記事のArduino同様、指定したピンに適当な電線をつなぎ、適当な抵抗と、おまけに出力が分かりやすいようLEDでもつければ完成です。
が、私の電波時計の場合、なんと内蔵LEDを点滅させるだけでも、M5Stick-Cと電波時計を密着させれば受信できてしまいました…。バッテリーでも動作するし、なんともお手軽。

f:id:NeoCat:20200724072513j:plain


M5Stick-C自体にも日時を表示しています。これだけですでにNTP時計になっていますが,まあ。。

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秒ごとに押しています。(これだけだとあまり意味はないですが、ゲーム等と組み合わせたり、使い方は工夫次第ということで。)

続きを読む