ラズパイで電球チカ

はじまり

 寝室のスマート電球をオンオフするのに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に登録して自動起動することも確認した。

 もしかしたらシェルスクリプトでもいけるかもしれん。