はじまり
寝室のスマート電球をオンオフするのにbluetoothシャッターを使えるようにした。
doroyamada.hatenablog.jp
しかし、bluetoothのペアリングに失敗することがたびたびあった。一旦失敗すると失敗が続く。
そこで対策として考えたのが、デッドストックと化しているRaspberryPi Zero WHにボタンをつけてコントロールすること。
物理的工作
まずは物理的工作。放熱を考えて密封は避ける。使ったのはダイソーで買ってきたプライスカード。折返し部分は切断。ホットメルトで接着。
L型カードスタンドjp.daisonet.com
制御プログラム関連の試行錯誤
最初の構想はphpのfile_put_contentsとfile_get_contentsで/sys/class/gpio/のファイルをいじる力技。引っかかったのは
permissionの不思議な挙動。
/sys/class/gpio/exportやunexportへの書き込みはできるが、/sys/class/gpio/gpioXの下にできたファイルへの書き込みがpermission不足でできない。
ファイルのユーザやパーミッションを見ても書き込めそうだし、プロンプトからechoコマンドでリダイレクトしてもエラーは出ない。
先人がいらした。
/sys/class/gpio/exportへの書き込みはできますが、/sys/class/gpio/gpio4/derectionへの書き込みができません。
いったんこれは断念。しかしあとで考えたら、service登録して実行させるつもりだったからrootで実行することを前提にしてもよかった。
プルアップ/プルダウンの設定
いくつかのページには「/sys/class/gpio/gpioX/directionにhighかlowを書き込めばよい」とある。が、実際にやってみると、どちらを書き込んでもoutになってしまう。inで使いたいのに。
これも先人がいらした。
調べている最中、「プルアップ(ダウン)を設定するために echo "high"("low") > /sys/class/gpio/gpiox/direction とする」という記述をネット上で見かけましたが、多分、これはダメな感じです。ソースコードを読んだところ、これは"出力ポートに設定した上で出力値を low または high に設定する"という動作のエイリアス(別名)になっており、ハイ インピーダンスの入力ポートにはなりません。
phpでの完全制御は諦めて、一部をraspi-gpio(先日bullseyeにアップグレードしたので入っている)に任せつつ、phpで全体を制御することにした。
ラズパイの定番はpythonだけど、慣れてないし、jsonの引用符の融通が利かない(個々の値はシングルクォーテーションでくくらないといけない)ので、避けたい。
raspi-gpioを検証
- /sys/class/gpio/gpioXがなくても動作する、というか、動作に/sys/class/gpio/gpioXが必須ではないと知った。
- プルアップ/プルダウン状態をgetすることができない。
動作スクリプト
メイン部分。
最初は/sys/class/gpio/を一部利用してvalueをfile_get_contentsしていた。
valueの値が変わったことを検知するため、ファイル変更を監視するinotify機能を使うことを考え、いろいろとインストールして使えるようになったものの、やってみると検知しない。ファイルを見ると、valueファイルを書き換えてもタイムスタンプが変わっていなかった。たぶんそれゆえに検知できないんだろう。
となると/sys/class/gpio/を使う必要がなくなるので、値はraspi-gpioで得ることにした。変化を知るために無限ループ(とりあえず0.05秒間隔)に。
#!/usr/bin/php <?php error_reporting(E_ALL); require_once(__DIR__."/bulb_bedroom_include.php"); $pinnum=26; exec("raspi-gpio set $pinnum ip pu"); sleep(1); while(true){ $ret=`raspi-gpio get $pinnum`; preg_match("/level=(\d)/",$ret,$tmp); $value=$tmp[1]; if($value == 0){ $json_return = meross_bulb("GET",1); $status = ($json_return->payload->togglex->onoff); if($status == 0 || $status == 1){ meross_bulb("SET",($status-1)*-1); }else{ file_put_contents("./log/" . date("Ymd_His") . "_error.log",$json_return); } sleep(1); #チャタリング対策 }else{ usleep(50000); } } ?>
呼び出している部分。固有値一部修正。methodがgetの場合、onoffの値は無視するもよう。
<?php $url = 'http://192.168.1.158/config'; $json_template = '{ "header": { "from": "http://192.168.1.158/config", "messageId": "8d4dca734ef6a13155d2efae06ee", "method": "__method__", "namespace": "Appliance.Control.ToggleX", "payloadVersion": 1, "sign": "cffb6d594ea32d4cf93d1e83eaa18", "timestamp": 1732580062, "triggerSrc": "AndroidLocal", "uuid": "2303209426534752050348e10a630" }, "payload": { "togglex": { "channel": 0, "onoff": __switch__ } } }'; function meross_bulb($method,$switch){ global $url,$json_template; $json=str_replace(array("__method__","__switch__"),array($method,$switch),$json_template); $opts = array( 'http'=>array( 'method'=>"POST", 'header'=>"Content-Type: application/json", 'content'=>$json ) ); $context = stream_context_create($opts); return json_decode(file_get_contents($url, false, $context)); }
無事動作。
ボタンを押し続けると1秒感覚でスマート電球が点滅する電球チカ状態に。
serviceに登録して自動起動することも確認した。
もしかしたらシェルスクリプトでもいけるかもしれん。