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

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

youtu.be

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

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

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

EXAPUNKSのソリティアをRubyで解くついでにJITを試してみる

プログラミングっぽい独特なパズルゲームを開発しているZachtronicsが出したEXAPUNKSというゲームで遊んでいます。
簡単なアセンブリのような言語でEXAというウイルスのようなエージェントをプログラミングし、ストーリーを追いながらハッキングでデータを改ざんしたりハードウェアを操作したり、時には別のハッカー(プレイヤー)のプログラムと対戦したりするというパズルゲームです。

store.steampowered.com
www.youtube.com

さて、本編もなかなかやりごたえのある内容(かつ適度な楽しめる難易度)なのですが、少しストーリーを進めると、おまけで息抜きにミニゲーム「ПАСЬЯНС」(ソリティア)で遊べるようになります。

数字カードは赤黒赤黒…と互い違いの色で降順に重ねることができ、絵のカードは同じスートのカードは重ねられるという条件を満たしつつ、一枚、またはこの条件で重ねたカードをまとめて移動して積み重ねていき、最終的に 10-9-8-7-6 という降順のセット4つと各スートの絵文字4枚を重ねたセット、計8セットを作れば勝利、というルールです。
なお、途中で一枚だけ、右上の空きスペースに避けておくことが可能です。
f:id:NeoCat:20190509074138p:plain

息抜き用とはいえ、適当にやっていると結構、途中で詰まってしまうくらいの難易度です(UNDOもできないし)。
せっかくのプログラミングゲームなので、このПАСЬЯНСをプログラムで解かせてみました。とはいえこのゲームのEXA用言語ではなく、普通のRubyです。

Solve ПАСЬЯНС puzzle in EXAPUNKS · GitHub

※ 以下のデータ入力と操作の自動化については次の記事で:
EXAPUNKSのソリティアを自動操作で解く - Okiraku Programming


入力として data.txt というファイルに、カードの並びを左上から右に記入したものを与えます。絵のカードは各スートを s h c d の文字で、数字のカードは赤6なら r6 黒10なら b10 のように記述します。上記のスクリーンショットであれば、以下のようになります。

r10 r8 b8 r6 h c c b7 r7
d b8 s d b6 b9 r9 b10 b10
c b9 r10 r7 c r8 h r6 d
s r9 d h s h b7 b6 s

これに対して solver.rb を走らせると、以下のように解法の手順が表示されます。

movable: 0 -> 4  ::  s -> s
movable: 0 -> 9  ::  c -> []
movable: 0 -> 2  ::  d -> d
movable: 3 -> 5  ::  h -> h
movable: 4 -> 8  ::  s-s -> s
movable: 7 -> 3  ::  b6 -> r7
...

:: の左が移動するカードの位置(0が左で8が右の山、9は右上の空きスペース)、:: の右はカードの数字や絵カードのスートを示します。
まず、0(一番左)のスペードの絵カードを4(真ん中)のスペードに重ねる。
次に、0(一番左)のクラブの絵カードを右上の空きスペースに退避する。

という感じです。

このままでも、通常は一瞬で、遅くとも数秒で解けますが、稀に延々計算していて解が表示されないことがあります。このプログラムでは素朴に手順を深さ優先で探索しているために、解が見つからない場合には最後の方の似たような手順を何度も調べ続けてしまうためです。
ランダムな配置でどれくらい解ける*1のか調べてみたところ、だいたい96%くらいは解けるようでした。

ちょっと素朴すぎるので、次のように適当に深読みしすぎるのを防止してやると、ほぼ確実に解けるようになります。

+$depth_limit = []
+
 def scan(deck, steps)
+  (0...(steps.length-1)).each do |depth|
+    return if ($depth_limit[depth] += 1) > 30 * 9 ** (steps.length - depth + 1)
+  end
+
   rem = 0
   (0..9).each do |i|
     unless deck[i].length == 0 || completed(deck[i])
+      $depth_limit[steps.length] = 0
       rem += 1
       scan_i(deck, i, steps)
     end


時間がかかる例として、以下の入力があります。

b10 r8 h b6 c b7 r10 r7 r7
h s b10 b6 d b8 r10 r9 s
c c d r6 s s d h r9
h b9 b8 b7 b9 d c r6 r8

depth_limitをつけた上で、手元のMacBook Proでだいたい10秒くらいかかります。せっかくCPUインテンシブな負荷なので、たまたま手元にあるRubyのバージョンごとに速度を測ってみました。

Rubyバージョン 所要実時間(3回平均) CPU時間(user+sys, 3回平均)
ruby 2.3.7p456 10.63s 10.59s
ruby 2.5.3p105 10.42s 10.38s
ruby 2.6.3p62 8.79s 8.77s
ruby 2.6.3p62 --enable-jit 8.39s 16.76s
ruby-trunk(2.7.0-dev 025206d0dd) 9.17s 9.13s
ruby-trunk(2.7.0-dev 025206d0dd) --enable-jit 8.13s 10.56s


JITで多少ですが実時間が早くなっていますね。
また、ruby-trunkではJITなしの時はv2.6.3よりも若干遅くなっていますが、JITありではさらに高速になっており、かつJIT時のCPU時間の上昇も抑えられているのが分かります。

*1:5秒以内に終了しない場合は解けないとみなしました

Macのテキストエディットやメモで入力される制御コード

macOS (Mojaveで確認) のテキストエディットやメモ、メールといった、標準テキスト入力を使ったアプリケーションでは、option や ctrl キーの組み合わせによって特殊な制御文字コードが入力される場合があります。これらは目に見えなかったり単なる改行に見えますが、処理するプログラムによっては思わぬところで文字化けや表示が崩れる原因になることがあるので、注意が必要です。

なお、通常の改行は LF (U+000A) です。

ctrl + enter (メモ帳などでは shift + enterも同様)

Unicode で定義された Line Separatpr (U+2028) が入力されます。
これは箇条書きなどの中で改行するために使われているようです。

ctrl + option + enter

CR (U+000D) が入力されます。単独でも改行されますが、直後に enter を押すと CR+LF となってこれも一つの改行としてレンダリングされます。

ctrl + option + アルファベット

C0 Control Codeに定義された制御コードが入力されます(記号は英語キーボードの位置に準拠するようです)。以下に一覧があります。

C0 and C1 control codes - Wikipedia

参考: Unicodeにおける空白文字

空白文字の一覧は以下にあります。

Whitespace character - Wikipedia

Line Separatpr (U+2028) はHTMLだと < BR > に相当するものとのこと。
Paragraph Separatpr (U+2029) はHTMLだと < P > に相当するそうですが、入力する方法は見つかりませんでした。
(といっても、例えばWebブラウザからこれらのタグのレンダリング結果をコピーしても単なる LF になります。)

プログラミング言語によって、これらが改行扱いされるかは異なるようです。場合によっては、これらが改行として扱われることを想定していないコードにおいて脆弱性を引き起こす原因になることもあるとか。

ESP32で2枚の64x32 LEDマトリクスパネルに128x32で描画してみた

以前の記事で64x32のLEDマトリクスパネルにESP32 (ESP-WROOM-32) を使って描画するライブラリを公開していました。これを改造してパネル2枚を連結した128x32に描画してみました。

www.youtube.com
f:id:NeoCat:20181101012325j:plain

使っているのは以前の記事とおなじパネル2枚とESP32です。
P3 RGBピクセルパネルHDビデオディスプレイ64×32ドットマトリックスSMD LEDディスプレイモジュール192×96mm

neocat.hatenablog.com


このパネルは裏面のコネクタに同一形状でINPUTとOUTPUTが付いており、付属のコネクターでデイジーチェーン接続することができるようになっています。
OUTPUTにはシフトレジスタの出力が繋がっており、INPUTから入ったデータが32bit分遅れて出て行くようになっているので、ライブラリからは同じインターフェイスで単に横幅が128ピクセルあるかのように扱うことができます。

f:id:NeoCat:20181101012813j:plain

改造したライブラリは128x32ブランチにあります。drawPixel() 関数の実装をいじれば、縦に並べて64x64にするといったことも可能でしょう。
なお転送するデータ量が増えるため、タイマー割り込みの頻度を少し遅くしてあります。これにより暗めの表示をするとちらついて見えることがあります。さらに枚数を増やすこともできるはずですが、よりちらついてしまいそうです。
GitHub - NeoCat/ESP32-P3RGB64x32MatrixPanel at 128x32

スマートコンセントをクラウドを使わずLANから制御してみた

最近のIoTブームでクラウドから制御できるコンセント(スマートプラグとかスマートコンセント)が安価に入手できるようになりました。どれもおおむね、専用のスマートフォンAppや、Amazon AlexaとかGoogle Home、IFTTTに対応しており、音声制御や自動化が可能になっています。それだけでも便利なのですが、LANに閉じて使いたいときもあるよね、ということで、クラウドに繋がらない状態でも制御できないかやってみました。
(何となく第三者のシステムから家庭内の機器を制御できる状態にしておきたくないとか、クラウドサービスが停止したら使えなくなるのがいやだという理由ですが。)

使ったもの

今回買ったのはこのHyletonという所の二つの製品。Amazonのタイムセールで一番安かったので。

Amazon.co.jp: Hyleton

f:id:NeoCat:20181029042557j:plain:w300 Wifiスマートプラグ スマートコンセント スマートソケット [2個セット]

f:id:NeoCat:20181029042605j:plain:w300 スマートコンセント WIFI制御 パワーストリップ プラグ4つ

後者の方は、USB電源2つ同時のON/OFFも制御することが可能です。

セットアップ

セットアップはSmartHomeというアプリを使って行い、Bluetooth経由で接続先のWi-Fi(2.4GHzのみ)の情報、およびクラウドやLANから制御するための暗号キーがコンセントに設定されます。

接続先を調べてみたところ、この製品は Tuya Smart というクラウドサービスを利用しているようです。また、常時クラウドにMQTTで接続しているものの、LAN内からも制御を受け付けており、スマホアプリも同一LANにある(UDPポート6666番のブロードキャストを見ることで検出可能)場合はLAN内で直接通信をしているようです。

Tuyaを利用しているデバイスは、有志が開発したNode.jsでLANから制御するためのNode.jsのライブラリがありますので、これを使ってLAN内から制御してみることにしました。この方法であれば、インターネットとの通信が遮断された環境や、MQTTがブロックされた状態であっても、制御が可能になります(ただし設定時は(少なくとも同一のSSID/パスワードで)インターネット接続可能なWi-Fiは必要ですが。)
github.com


セットアップする際、スマホから設定されるキーを取得してやる必要があります。キーはHTTPSで暗号化されてクラウドから渡されるので、少々面倒ですが、Tuyaapiのセットアップ手順通り、Charles というHTTPSプロキシを使ってスマホの通信内容を解析してキーを取り出しました。Charlesのセットアップとスマートフォンに独自CA証明書を信頼させる手順は以下が詳しく参考になります。このiPhoneAndroidの手順を踏めばOKです。

通信系のデバッグには Charles が便利

HTTPS通信の内容が取得できるようになったことを確認の上で、スマホアプリから設定操作をやり直すと、キーが取得できます。

制御してみる

あとは、tuyapi を導入し、

npm install tuyapi

単体のプラグであれば smart_plug.js として

#!/usr/bin/env node
const TuyaDevice = require('tuyapi');
setTimeout(() => { process.exit(1) }, 10000);
// 取得したID/キーを以下に設定
var tuya = new TuyaDevice({
  id: 'xxxxxxxxxxxxxxxxxxxx',
  key: "xxxxxxxxxxxxxxxx",
});
// IPアドレスを検出
tuya.resolveIds().then(() => {
  // 引数に応じてON/OFFを設定
  tuya.set({set: process.argv[2] == '1'}).then(result => {
    console.log(result ? 'OK.' : 'failed.');
  });
});

killtimer.unref();

というコードを書いて

./smart_plug.js 1    # ON
./smart_plug.js 0    # OFF

とコマンドを実行することでコンセントのON/OFFができます。

ちなみに、上記ではブロードキャストを受けてIPアドレスを探すため、若干待ちが発生しますが、IPがわかっているならば指定しておくことで tuya.resolveIds() を飛ばして高速化も可能です(以下参照)。

複数のコンセントがあるタップであれば、 smart_tap.js として

#!/usr/bin/env node
const TuyaDevice = require('tuyapi');

var t = setTimeout(() => { process.exit(1) }, 10000);

var tuya = new TuyaDevice({
  id: 'xxxxxxxxxxxxxxxxxxxx',
  key: "xxxxxxxxxxxxxxxx",
  ip: "192.168.xx.xx",  // IPを指定することで高速化
});
// 現在の設定を取得
tuya.get({schema: true}).then(result => {
  console.log(result)
});
// 引数に応じてON/OFFを設定
tuya.set({dps: process.argv[2], set: process.argv[3] == '1'}).then(result => {
  console.log(result ? 'OK.' : 'failed.');
});

t.unref();

というようなコードを書けば、

./smart_tap.js 1 1    # 1番のコンセントをON
./smart_tap.js 1 0    # 1番のコンセントをOFF
./smart_tap.js 5 1    # USB電源のON
./smart_tap.js 6 0    # すべての電源をOFF

などとコマンドを叩くことでLANから制御が可能です。
(IPがわからない・不定の場合は上のコードのように tuya.resolveIds().then(() => { ... }) 内で get, set を行う必要があります。)


ちなみにおまけとして、上記のコマンドを HomeBridge から実行できるようにするか、 homebridge-tuyapi(1つのコンセントのみ対応) を使えば、Siriから制御することも可能です。

ESP32で64x32 LEDマトリクスパネルを制御

ESP32を使って、Amazonで安価に売られている64x32ピクセルのLEDマトリクスに色々表示できるようにしてみました。

f:id:NeoCat:20180502031918j:plain

 
ESP32 + 64x32 RGB LED Matrix 

使ったもの

ESP32-DevKitC

P3 RGBピクセルパネルHDビデオディスプレイ64×32ドットマトリックスSMD LEDディスプレイモジュール192×96mm 

 f:id:NeoCat:20180502205034p:plain

あとは適宜ブレッドボードなど。 

配線

続きを読む

Bluetoothで値が読めるUSB電圧・電流モニター「UM24C」を使ってiPhone Xの充電状況を測ってみる

Bluetooth接続ができるUSB電圧・電流テスター「UM24C」を買ってみました。

公式のAndroid端末やWindows用のソフトを使って値を読み出すことができます。が、自作のプログラムからこれを読み出してみました。

 

f:id:NeoCat:20180414222359p:plain

カラーLCDがついていてなかなか派手ですが見やすい。上下に左右2つのボタンがついていて、これで操作します。操作HELP表示(英語)つき。これ単体で電圧や電流変化のグラフを見ることもできたりして、なかなか高機能です。

入手方法

Amazonでも購入できます

慣れてる人ならAliExpressで購入した方が安く買えます。Alibaba グループ | AliExpress.comの 電圧メートル からの UM24C 2.0カラー液晶ディスプレイusb電圧テスター電流計電圧計amperimetroバッテリー充電ケーブル抵抗30% 中の UM24C 2.0カラー液晶ディスプレイusb電圧テスター電流計電圧計amperimetroバッテリー充電ケーブル抵抗30%

↑ UM24 (Cがつかない) も選べますが、そちらはBluetooth通信機能がありませんのでご注意を。購入から1週間くらいで届きました。

※ 上位版?のUM25Cというのも出ているようですね。USB Type-Cボートがついているらしい。

早速読み出してみる

USB電源につないだ状態でBluetoothバイスを表示させてみると、普通のSPP(シリアルボートプロファイル)として見えます。

Windowsマシンでは、UM24Cを追加すると、PINを聞かれ、「1234」を入力すれば接続できます。COMボートが2つ追加され、どちらか一方に接続して利用します。公式のソフトがあり、これを導入すれば良いのですが、これがNational Instrumentsの計測プラットフォーム(NI-VISA)が丸っと入るため、かなり巨大です。

Macではペアリング自体は簡単にできるのですが、追加されるPort ( /dev/tty.Port-UM24C )に接続してもデータが読めず。(Windowsでも2つのうち1つのCOMポートは全く応答がないので、これだけが見えてしまっているのでしょうか…?)

USB-Bluetoothアダプタを接続したLinuxボード (Debianで試しました) からは以下の手順で接続できました。

$ sudo apt install bluez bluez-utils # 必要なパッケージの導入

$ sudo bluetoothctl -a

・デバイスをスキャン

[bluetooth]# scan on 

[NEW] Device 00:BA:55:XX:XX:XX UM24C

・発見されたUM24Cをペアリング

[bluetooth]# pair 00:BA:55:XX:XX:XX

Attempting to pair with 00:BA:55:XX:XX:XX

Request PIN code

[agent] Enter PIN code: 1234

[CHG] Device 00:BA:55:XX:XX:XX UUIDs: 00001101-0000-1000-8000-00805f9b34fb

[CHG] Device 00:BA:55:XX:XX:XX Paired: yes

Pairing successful

[bluetooth]# trust 00:BA:55:XX:XX:XX

[CHG] Device 00:BA:55:XX:XX:XX Trusted: yes

Changing 00:BA:55:XX:XX:XX trust succeeded

・rfcommで接続

sudo rfcomm bind 0 00:BA:55:XX:XX:XX

これで、 /dev/rfcomm0 として UM24C に接続できるようになりました。

 

あとは、適当なプログラムでこのポートを開き、「0xf0」の1バイトを送信すると、130バイトほどのレスポンスが返ってきます。なお jerm コマンドは jerminal というターミナル接続ソフトです。

$ (printf "\xf0"; sleep 2) | jerm /dev/rfcomm0 | xxd
...

00000000: 0963 0207 000a 0000 0033 0016 0048 0000  .c.......3...H..

00000010: 0000 020b 0000 09da 0000 0003 0000 000f  ................

00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................

00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................

00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................

00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................

00000060: 0030 0030 0000 0000 0000 0000 0000 000a  .0.0............

00000070: 0000 0000 0000 0001 0002 0000 1446 0000  .............F..

00000080: fff1                                     ..

16bit Big Endian値が並んでいる感じで、最初は固定値?0x963、
電圧 0x207 = 519 ->  5.19V
電流 0x0a = 10  -> 0.010 A

他にも電流積算値や抵抗値など、画面上に出ているものは大体そのまま全て並んでいることがわかります。

ちなみに、他にも「0xf1」を送ると画面切り替え、「0xf2」を送ると画面回転、「0xf3 」「0xf4」で積算値グループの切り替えやクリアができたりします。

プログラムでロギングしてみる

Rubyでログを記録するプログラムを書いてみました。

#!/usr/bin/ruby                                                                                     

require 'serialport'

require 'timeout'

require 'json'

 

Signal.trap(:PIPE) { exit }

 

sp = SerialPort.new('/dev/rfcomm0')

sleep 2

failure = 0

loop do

  sp.write "\xf0"

  begin

    Timeout.timeout(2) do

      s = sp.read(130)

      if !s || s.length < 130

        warn 'incomplete data'

        failure += 1

        next

      end

      data = s.unpack('n*')

      # p data                                                                                      

      json = JSON.generate({

        T:   Time.now,

        V:   data[1]/100.0,

        A:   data[2]/1000.0,

        W:   data[4]/1000.0,

        C:   data[5],

        mAh: 256 * data[8] + data[9],

        mWh: 256 * data[10] + data[11],

        ohm: data[62]/10.0,

      })

      puts json

      STDOUT.flush

      failure = 0

    end

  rescue Timeout::Error

    warn 'timeout'

    failure += 1

  end

  if failure > 5

    warn 'Too many failure. quit.'

    exit 1

  end

  sleep 1

end

これを実行すると1秒ごとにJSON形式で計測値が出力されます。

{"T":"2018-04-14 23:01:48 +0900","V":5.17,"A":0.089,"W":0.46,"C":21,"mAh":6,"mWh":31,"ohm":58.0}

{"T":"2018-04-14 23:01:49 +0900","V":5.17,"A":0.089,"W":0.46,"C":21,"mAh":6,"mWh":31,"ohm":58.0}

{"T":"2018-04-14 23:01:50 +0900","V":5.17,"A":0.089,"W":0.46,"C":21,"mAh":6,"mWh":31,"ohm":58.0}
...

 

 iPhone Xの充電状況を計測してみる

これを使ってiPhone Xの充電中の電流などを計測してみました。

なお、iPhoneのバッテリー残量は、MaciPhoneを接続した状態で libimobiledevice を使って取得することができます。

$ brew install libimobiledevice  # インストール
idevice_id -l
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  # <- IDを確認

$ ideviceinfo -u XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -q com.apple.mobile.battery

BatteryCurrentCapacity: 96       # <- バッテリー残量

BatteryIsCharging: false

ExternalChargeCapable: false

ExternalConnected: false

FullyCharged: false

GasGaugeCapability: true

HasBattery: true

 

 

これを定期的に記録して、UM24Cで測った計測値と合わせてグラフ化してみました。
以下が残り5%の状態から100%までの、2時間半の充電中の状況です。

f:id:NeoCat:20180414230918p:plain

ちょっと単位が入り混じってしまっていますが、92-93%くらいまで充電されたところで充電電流がカクッと減る様子などがみて取れます。

なお、電流積算値はiPhoneアプリ 「Battery Care」を使って取得した最大容量 2700mAhと結構ぴったり一致しました。(iPhone Xの最大容量は2800mAhですが、使っているうちに僅かに劣化したようです。) とはいえ充電中も電源はONなので、iPhoneの消費と誤差がたまたま相殺したのでしょうかね。