FlashAir(W-04)の画像をGoogle Photos(Picasa Web)のアルバムにアップロードする

カメラをEOS7D Mark IIに新しくしたら、SDカードがUHS-I対応と言うことで、Wi-Fi SDカードもUHS-I対応のFlashAir W-04に変更しました。

f:id:naoyukinagano:20180102102210j:plain

そこで、luaスクリプトを使ってFlashAirから直接Google Photosにアップロードを試みたのですが、何度やってもエラー。どうやらPicasaweb APISSL証明書のサイズが大きいらしく、ネゴシエーションエラーを起こすようです。

 

seesaawiki.jp

そこで、方向を変え、PQI Airと同様、IPアドレスを固定とし、サーバーからFlashAirに対して写真をダウンロードしに行き、サーバーからGoogle Photosにアップロードすることにしました。

 

まずはIPアドレスを固定します。

FlashAirのCONFIGで設定します。

/SD_WLAN/CONFIGに[Vender]とは別に新たに[WLANSD]というセクションを設けて、IPアドレス等を指定します。

私の場合は192.168.x.161としました。(xは皆さんの環境に合わせてください。)

ルーターは192.168.x.1です。

IDは適当な名称をつけてください。

 [WLANSD]

ID=Flashair_999
DHCP_Enabled=NO
IP_Address=192.168.x.161
Subnet_Mask=255.255.255.0
Default_Gateway=192.168.x.1
Preferred_DNS_Server=192.168.x.1
Alternate_DNS_Server=8.8.8.8

 

で、STAモード(インフラストラクチャモード、つまり子機となってWi-Fiアクセスポイントに接続しにいくモード)で接続します。

私は次のサイトを参考にさせてもらいました。

database-tearoom.seesaa.net

そして、サーバー側のスクリプトを書きます。

手順としては、192.168.x.161を死活監視します。

もしhttpの応答があったら、/DCIM直下にあるindex.htmlの中から指定した名称が入ったフォルダのリストを抽出して配列に格納します。

通常、カメラは/DCIMの配下にカメラの機種名かメーカー名が入ったフォルダを作成し、その配下に9999枚までのファイルを保存していきます。たとえば私の場合はxxxEOS7Dという感じ。xxxは数字3桁ですね。

これをキーとして抽出することで、FlashAirの制御用画像を対象外にすることが出来ますし、どれだけフォルダが出来ても全てスキャン出来ます。

それぞれの配下のindex.htmlからファイルリストを取得します。

ファイルを一つづつflashair_done.txtの中にあるかないかをgrepで検証し、あれば処理を無視、なければwgetでFlashAirからダウンロード。

そして2048pxに縮小、Google Photosにアップロード、flashair_done.txtに書き出し、exiftoolで日付ディレクトリに移動させて、次のファイルの処理を行います。

 

アップロードの処理については前回PQI Airの際に作成した"upload_images_in_dir.sh"に任せます。

gentoolinux.hatenablog.com

 

必要なアプリケーションとしてはsedwgetImagemagick、exiftoolです。

 

スクリプトはこうなりました。

FLASHAIR_IP=にはFlashAirのIPアドレスを、

DCIM_FOLDER=にはカメラがDCIMの配下に作成するファイル名の3桁の数字を除いた文字列を入れてください。

 

#!/bin/bash

#初期変数一覧

#Flashairのアドレス
FLASHAIR_IP="http://192.168.x.161"

#DCIMフォルダの名称
DCIM_FOLDER="CANON"


#現在のディレクトリに移動
cd `dirname $0`

#プロセスIDを保存
echo $$ > pid.txt

#DCIMフォルダのURLを生成
WGET_PRE_URL=${FLASHAIR_IP}"/DCIM/"

 

while :
do

#Flashairを死活監視
ALIVE=$(/usr/bin/wget -nv --spider --timeout 60 -t 1 ${FLASHAIR_IP} 2>&1 | grep -c '200 OK')

#もし死活監視で生きていたら
if [ $ALIVE -eq 1 ]; then

#配列を初期化
echo "Flashair - Format Arrys."
DCIM_LIST=()
JPG_LIST=()

#DCIM配下のディレクトリを配列に代入
echo "Flashair - Get under DCIM directory list."
DCIM_LIST=(`/usr/bin/wget ${WGET_PRE_URL} -q -O - | grep 'wlansd.push({"r_uri"' | sed -e 's/wlansd.push({"r_uri":"//' -e 's/", "fname":"/\//' -e 's/", "fsize".*//' | grep ${DCIM_FOLDER}`)

#フォルダの数だけループ処理を実施
echo "Flashair - Starting loop under DCIM directory."
DCIM_LOOP=${#DCIM_LIST[@]}
DCIM_LOOP=$*1
while [ $DCIM_LOOP -ge 0 ];
do

#フォルダの中のファイルを配列に代入
echo "Flashair - Get file list under "${DCIM_LIST[DCIM_LOOP]}
JPG_LIST=(`/usr/bin/wget ${FLASHAIR_IP}${DCIM_LIST[${DCIM_LOOP}]} -q -O - | grep 'wlansd.push({"r_uri"' | sed -e 's/wlansd.push({"r_uri":"//' -e 's/", "fname":"/\//' -e 's/", "fsize".*//'`)

#ファイルの数だけループ
echo "Flashair - Starting loop under "${DCIM_LIST[DCIM_LOOP]}
JPG_LOOP=${#JPG_LIST[@]}
JPG_LOOP=$*2
while [ $JPG_LOOP -ge 0 ];
do

#アップ済みのファイルかどうかをチェック
echo "Flashair - Check FLASHAIR_DONE "${JPG_LIST[JPG_LOOP]}
if ! `cat flashair_done.txt | grep -q "${JPG_LIST[JPG_LOOP]}"` ; then

#アップ済みになかった場合の処理
#wgetによるファイルの取得
echo "Flashair - Get file "${JPG_LIST[JPG_LOOP]}
/usr/bin/wget -q -N --no-host-directories --no-directories ${FLASHAIR_IP}${JPG_LIST[JPG_LOOP]}

#ダウンロードした全てのJPGを2048pxにリサイズ
echo "Flashair - Resize "${JPG_LIST[JPG_LOOP]}
/usr/bin/mogrify -resize 2048x2048 -quality 100 *.JPG

#ディレクトリ上にあるJPGファイルをpicasaweb APIでアップロード
echo "Flashair - Uploading "${JPG_LIST[JPG_LOOP]}
./upload_images_in_dir.sh

#ディレクトリ上にあるJPGファイルを日付ディレクトリで整理
echo "Flashair - Move YMD directory "${JPG_LIST[JPG_LOOP]}
/usr/bin/exiftool -q '-Directory < CreateDate' -d %Y%m%d *.JPG

#flashair_done.txtにファイル名を追加
echo "Flashair - Output to flashair_done.txt "${JPG_LIST[JPG_LOOP]}
echo ${JPG_LIST[JPG_LOOP]} >> flashair_done.txt

fi

JPG_LOOP=$*3
done


DCIM_LOOP=$*4
done

fi

sleep 10
done

 

 

ちなみに、upload_images_in_dir.shの中身はこんな感じです。

#/bin/bash

CLIENT_ID="xxxxxx.apps.googleusercontent.com"
CLIENT_SECRET="yyyyyy"
REFRESH_TOKEN="zzzzzz"
USER_ID="" #googleアカウントのユーザー名
ALBUM_ID="aaaaaaaaa(全て数字)"

#アクセストークン取得
curl -s --data "refresh_token=${REFRESH_TOKEN}" --data "client_id=${CLIENT_ID}" --data "client_secret=${CLIENT_SECRET}" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token > token.JSON
ACCESS_TOKEN=`cat token.JSON | jq -r '.access_token'`


ENDPOINT="https://picasaweb.google.com/data/feed/api/user/${USER_ID}/albumid/${ALBUM_ID}?access_token=${ACCESS_TOKEN}"

DIR="./" # 任意のディレクトリを指定

IFS_BAK=${IFS}
IFS="
"
FILES=`ls -1 ${DIR}`
for FILE in ${FILES}
do
if [ "${FILE}" != "" ]; then
TYPE="UNKNOWN"
case "${FILE}" in
*\.bmp) TYPE="image/bmp";;
*\.JPG | *\.jpg) TYPE="image/jpeg";;
*\.png) TYPE="image/png";;
*) TYPE="UNKNOWN";;
esac
# echo "$TYPE $FILE $LENGTH"
if [ "${TYPE}" = "UNKNOWN" ]; then
continue;
fi
LENGTH=`ls -l "${DIR}/${FILE}" | tail -n1 | sed -E 's/ +/ /g' | cut -d' ' -f5`
curl -s -XPOST "${ENDPOINT}" \
-H "Content-Type:${TYPE}" \
-H "Content-Length:${LENGTH}" \
-H "Slug:${FILE}" \
--data-binary "@${DIR}/${FILE}" > /dev/null
sleep 1
fi
done
IFS=$IFS_BAK

 

うーん。このインデントがなくなるの、なんとかならないかなぁ。

*1:DCIM_LOOP - 1

*2:JPG_LOOP - 1

*3:JPG_LOOP - 1

*4:DCIM_LOOP - 1

SonoffとESP EasyでHomekit対応格安温度・湿度センサーをつくる

さて、以前にHomebrideでマイナス表示をするための設定を書きました。

gentoolinux.hatenablog.com

よく考えたら、この記事の元になる気温を取得するデバイスを紹介していなかったことに気づきました。

制作過程を含めて紹介します。

 

まず、Amazonで次の品物を入手しました。

 おなじみのSonoffです。

今回はリレーとして使うわけではありません。もったいないかもしれませんが、ケースもACアダプタも別途用意する必要がないため、かえって割安になると判断しました。

 

 温度湿度センサーのDHT22です。安い割に精度がそこそこ良いそうです。

 

f:id:naoyukinagano:20171214225017j:plain

屋内用と屋外用に2セット購入。

(右上のUSBタイプLEDむき出しのライトはオマケ)

他に、ジャンパー線や10kΩ程度の抵抗、電源ケーブルとコンセントが必要ですが、ありもので済ませました。

 

まずはピンヘッダーを半田付けし、ESP Easyを書き込みます。

詳しくは以前の記事を

gentoolinux.hatenablog.com

このときは、リレーを使って照明をON/OFFさせていましたが、今回はあの、余っているGPIOピンを使います。

使うのは、5V、GND、GPIO14の3つ。

f:id:naoyukinagano:20171214225801j:plain

これは、赤が5V-VCC、グレーがGND、青がGPIO-14です。

 

DHT-22側に5V-VCCとDATA端子の間に10kΩのプルアップ抵抗をつけておきます。

f:id:naoyukinagano:20171214230112j:plain空中半田なので、かなり汚いですね。

こんなことにならないよう、基板が付いたDHT-22の購入をお勧めします。

 

たとえばコレでしょうか?

 

で、コンパクトになるように、ケースの横にネジ止めします。

使ったネジは余っていた何かの残りのネジです。

f:id:naoyukinagano:20171214230507j:plain

あまり長いとケースを突き抜けて基板を痛めるし、短いと届きません。

 

電源ケーブルなどを接続して完成したのがこれ。

f:id:naoyukinagano:20171214230605j:plain

 

では、ESP Easyの設定です。IPアドレス等は設定されている前提です。

まず、Hardwareページです。

f:id:naoyukinagano:20171214231702p:plain

ほぼDefaultのままですが、WiFi Status Led:をGPIO-13に割り当てて、LEDがWiFiのステイタスを表示するようにしました。

 

続いてDevicesページのTask1をEditボタンを押してDeviceを定義します。

f:id:naoyukinagano:20171214231905p:plain

Device:のドロップダウンから、"Temperature & Humidity - DHT"を選択すると、設定項目がずらっと出てきます。

Nameにはわかりやすい名前を、Deley:は60のままいじらず、IDX / Var:は適当に他とかぶらないように(といっても、MQTTプロトコルで利用する数値なので、HTTPで使う場合は関係ありません。)、1st GPIOはDHT-22を接続している"GPIO-14"を選択、DHT Type:は接続しているDHT-22を、Send Data:はHTTPで使うならチェックの有無どちらでも良い気がします。

下段のFormula temperature:は、気温の補正式を入れます。センサーから読み取った値が”%value%”という変数に入っています。これに対し、四則演算などで補正して出力することが可能です。私の環境では、ベランダに置いているのですが、室内の熱が伝わるせいで、アメダスの気温よりも高めに表示されるため、7度低く出力されるように”%value%-7”を入力しています。Decimals:は小数点第何位まで出力するかです。

Formula humidity:は湿度の補正式ですが、特に補正していません。

Value Name 1:は、JSON形式で出力する際の左辺の文字列です。デフォルトでは"Temperature"と、先頭が大文字ですが、homebridgeのHTTP気温センサプラグインが"temperature"と、小文字で認識するようなので、"temperature"と、小文字に変更しています。

Value Name 2:も同様に"humidity"と小文字にしています。

 

Submitで確定しCloseでDevicesに戻り、Task 2をEditします。

f:id:naoyukinagano:20171214235946p:plain

Task2は、Sonoffについているスイッチを、リブートスイッチにするというもので、たいして重要ではありません。

上記の通り設定してSubmitしCloseで閉じます。

 

できあがったDevices画面がコチラ。

f:id:naoyukinagano:20171215000119p:plain

右側にtemperature:とhumidity:の値が表示されていれば成功です!

 

あとは、リブートスイッチで再起動するように、Rulesに以下の記述をします。

on rebootswitch#Switch do
reboot
endon

 

これでSonoffとESP Easyによる温湿度センサーは完成です。

この気温と湿度をiOSのHomekit(ホームアプリ)に表示させるべく、homebridgeにプラグインを組み込んで設定します。

使ったプラグインは、"homebridge-http-temperature-humidity"です。

www.npmjs.com

まずはインストール。

gentoo # npm install -g homebridge-http-temperature-humidity 

で、config.jsonに設定を書きます。

ESP EasyにはDevicesのTask番号を指定することで、その値をJSON形式で返してくれる機能があります。これを利用します。(urlのxとyは環境に合わせて変えましょう。)

{
"accessory": "HttpTemphum",
"name": "Outside Temperature",
"url": "http://192.168.x.y/json?tasknr=1"
}

 accessoryの値が" homebridge-http-temperature-humidity"ではなく"HttpTemphum"ということに注意します。

ちなみにこのhttp://192.168.x.y/json?tasknr=1にブラウザからアクセスすると

TaskName: "OutsideTemp"
temperature: -3.8
humidity: 62.5 

 とこんな風に返ってきます。この値をhomebridgeが読み取って、iOSのHomekitに返しているようです。

homebridgeを再起動してiOSのHomekitを見てみると・・・。

f:id:naoyukinagano:20171215001800j:plain

気温と湿度が表示されました。ちなみに、小数点は四捨五入するようです。

Broadlink RM mini3を使ってhomebridgeでRoombaを動かす

Broadlink RM mini3という格安のWifiコントロールの赤外線リモコンを導入。

スマホアプリから直接コントロールすることはないだろうと、並行輸入品を購入すると、なんと、アプリからじゃないとWiFiSSIDの指定すら出来ないという仕様。スマホ以外から初期設定することができないようです。

で、並行輸入品は国内配信されているアプリは使えないようになっています。

なんとかして海外製アプリをAPKから入れてWiFi接続設定をすることに。

参考にしたのはこのサイト

obakasanyo.net

で、Wifiに接続できたらhomebridgeのbroadlink-rmプラグインをインストールして設定していきます。

gentoo # npm install -g homebridge-broadlink-rm

 

まずはルンバのCLEANボタンをリモコンで飛ばしたいと思い、認識させます。

f:id:naoyukinagano:20171209203623j:plain

 

と、思ったら、うんともすんとも言いません。

f:id:naoyukinagano:20171209201908j:plain

裏を見ると、ケータイでおなじみの逓信省マークが!

どうやらRF(無線)タイプのリモコンだったようです。

そこで、一か八か、ヤフオクでルンバの赤外線リモコンを落札しました。それがコチラ

f:id:naoyukinagano:20171209202032j:plain

 

とりあえず、config.json

"platforms": [
{
"platform": "BroadlinkRM",
"name": "Broadlink RM"

}]

だけを設定すると、HomeKitアプリにLearnスイッチが現れるはずです。

f:id:naoyukinagano:20171209202927j:plain

 

このLearnスイッチを押して5秒以内にRM mini 3に向かって学習したいリモコンのボタンを押すと、homebridgeのログにリモコンコードが表示されます。

ルンバのボタンのコードはコチラ

 

CLEANボタン

26005a00622021611f611f615f21206120611f0002a96121206120611f615f21206120611f0002a8622120611f621f615f2120611f61200002a8612220611f6120615f2120611f61200002b06220205f225f215f61212061206020000d050000000000000000000000000000

 

・・・ボタン(500シリーズ以降はDOCKとして機能)

26005a00622021602061206020616020216060000270622021602061206020616020216060000268622021602061206020615f2021615f0002696220216020611f6120615f2021615f0002696220206120611f6120606020216060000d050000000000000000000000000000

 

これをconfig.jsonに登録します。(ONでCLEAN、OFFでDOCKです。)

"platforms": [
{
"platform": "BroadlinkRM",
"name": "Broadlink RM",
"accessories": [
{

"name":"Roomba",
"type":"switch",
"data":{
"on":"26005a00622021611f611f615f21206120611f0002a96121206120611f615f21206120611f0002a8622120611f621f615f2120611f61200002a8612220611f6120615f2120611f61200002b06220205f225f215f61212061206020000d050000000000000000000000000000",
"off":"26005a00622021602061206020616020216060000270622021602061206020616020216060000268622021602061206020615f2021615f0002696220216020611f6120615f2021615f0002696220206120611f6120606020216060000d050000000000000000000000000000"
}
}
]
}
]

インデントがなくなると読みにくいですね。

これで、HomeKitからルンバが動くようになりました。

が、ルンバの赤外線受信感度が悪い・・・。

Homebridgeでマイナスの温度を表示する

Homebridgeで温度を表示するプラグインが多数ありますが、そのうちのいくつかはマイナス表示が出来ません。

これはHomebridgeのデフォルトではマイナスを扱わないから。

プラグインでオーバーライドすることで、マイナスを扱うことが出来ます。

基本的には、

service.getCharacteristic(変数).setProps({minValue: -100});

という感じで、.setProps({minValue: -100})をプロパティにセットすることでマイナスを扱える。

 

homebridge-http-temperature-humidityプラグインの場合、index.jsの98行目あたりに.setProps({minValue: -100, maxValue: 100})を追加すると良い。

temperatureService = new Service.TemperatureSensor(this.name);
temperatureService
.getCharacteristic(Characteristic.CurrentTemperature)
.setProps({minValue: -100, maxValue: 100})
.on('get', this.getState.bind(this));
services.push(temperatureService);

ん? GitHubに上がっている最新版は.setProps({ minValue: -273, maxValue: 200 })が入ってますね。

最新版はこんなことしなくて良さそうです。

 

 homebridge-advanced-http-temperature-humidityプラグインの場合94行目の後ろに追加します。

.getCharacteristic(Characteristic.CurrentTemperature).setProps({minValue: -100, maxValue: 100})

 これで、マイナスも表示されます。

北国の強い味方です。

 

Raspberry pi zero wとhomebridgeとHomeKitの連携その2

先日、Raspberry Pi Zero Wで、リビングのライトのスイッチを、壁のプッシュスイッチからも、iOSのHomeKitからもON/OFFできるように、まずはRaspberry Piに接続したプッシュスイッチでリレーを、とりあえずON/OFF出来るようにしました。

gentoolinux.hatenablog.com

しかし、このときはadd_event_detectをうまく使えず、while:ループの中で細かなsleepでごまかしながら力業で、プルアップされたGPIOインプットがGNDに落ちたかどうかを検出していました。

sleepを挟んでいるとはいえ、かなりCPUリソースを消費していたようです。

今は良いですが、夏は熱でスタックしそう・・・。

f:id:naoyukinagano:20171121223443j:plain

 

で、add_event_detectで立ち上がりエッジを検出後にだけ、該当のGPIOインプットがGNDに落ちているかを判定し、該当のインプットなら処理続行、該当のインプットでなければ処理中断をしてあげれば、問題なく動かせることに気づきました。

そこで、スクリプトの書き直しです。

また、killコマンドで終了したときにもしっかりとGPIOのクリーンナップが出来るように、シグナルハンドラも入れてみました。

こんな感じになりました。

#! /usr/bin/python

 

#必要なモジュールをインポートします。requestsはその1の記事を参照してインストールしてください。

import signal
import sys
import RPi.GPIO as GPIO
import time
import requests

 

#スイッチのGPIOとリレーのGPIOを指定します。

switch = 23
relay = 26

 

#リレーはwebiopiのほうからコントロールします。

#なぜならRPiライブラリでGPIO.setupすると、webiopiからはコントロール出来なくなるからです。

#リレーのURLを生成するための変数です。

url_front = 'http://localhost:8000/GPIO/'
url_back = '/value'
url_function_out = '/function/out'
url = url_front + str(relay) + url_back
url_function = url_front + str(relay) + url_function_out
url0 = url + '/0'
url1 = url + '/1'

 

#スイッチにつながったGPIOをプルアップされたインプットとして設定します。

GPIO.setmode(GPIO.BCM)
GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_UP)

 

#webiopiからリレーに接続されているGPIOをアウトプットとして設定します。

#設定にはPOSTメソッドを使います。
gpio_function = requests.post(url_function)

 

#エッジ検出後の動作をコールバック関数として記述します。

def switch_callback(switch):

    if GPIO.input(switch)==0:    #スイッチのGPIOがGNDに落ちているかどうかを判定。落ちていたら次の処理へ
         status = requests.get(url)    #リレーのGPIOの状態をwebiopi経由で取得するためにurlをgetで叩く
         if status.text=='1':    #getでリクエストしたBODY部分は.testに入るので、その値が(文字の)1かどうか判定
             requests.post(url0)    #1ならwebiopi経由で0にするためにPOST
             time.sleep(0.5)    #誤動作防止のため0.5秒スリープ
         else:
             requests.post(url1)    #getでリクエストしたBODYが1以外(つまり文字の0)だったら、webiopi経由で1に
             time.sleep(0.5)    #誤動作防止のため0.5秒スリープ

 

#killを受け取ったときの動作を関数として記述

def handler(signal, frame):   
     GPIO.cleanup()    #設定したGPIOを解放(解放しないと次に使えない)
     print "switch.py terminated for GPIO",switch
     sys.exit(0)    #終了

 

#スイッチとなるGPIOのエッジ(電位差の立ち上がりか立ち下がり)を検出したらswitch_callback関数を呼び出す。

GPIO.add_event_detect(switch, GPIO.RISING, callback=switch_callback, bouncetime=300)

#killされたらhandler関数を呼び出す。

signal.signal(signal.SIGTERM, handler)

 

#エラーが検出されても続行させる。エラー検出後の動作はexceptに記載

try:

     while True:    #永遠とループ
     time.sleep(0.1)    #イベントかエラーが出るまで0.1秒スリープの繰り返し

 

#ctrl+Cで中止が発生した場合の処理

except KeyboardInterrupt:
     print '\nswitch.py Stopped'
     GPIO.cleanup()    #設定したGPIOを解放(解放しないと次に使えない

 できるだけ細かくコメントを書いてみました。

実際にはコメントははずしています。

30分で0.5℃、CPU温度が下がりました。誤差かな?

 

さて、homebridgeの記載をwebiopiに合うように直します。

config.jsonの該当部分のみを出してみましょう。

{
"accessory": "Http",
"name": "Spot and Bracket Light",
"switchHandling": "realtime",
"http_method": "POST",
"on_url": "http://192.168.x.y:8000/GPIO/26/value/0",
"off_url": "http://192.168.x.y:8000/GPIO/26/value/1",
"status_url": "http://192.168.x.y:8000/GPIO/26/value",
"status_on": "0",
"status_off": "1",
"service": "Switch",
"brightnessHandling": "no",
"brightness_url": "",
"brightnesslvl_url": "",
"sendimmediately": "",
"username" : "",
"password" : ""
},

IPアドレスのx.yはご自身の環境に合わせて読み替えてください。

http_methodですが、EspEasyの場合はgetメソッドでコントロールしていたのに対し、webiopiはPOSTメソッドでコントロールします。

URLの中にある26は、対象のリレーが接続されているGPIOです。

サインスマートのリレーは0でON、1でOFFなので、"status_on": "0","status_off": "1"を明確に定義しています。

ServiceはLightにした方がよかったかも・・・。

 

これでhomebridgeを再起動すると、壁スイッチからもHomekitからも使えるようになりました。

しかも、EspEasyと違い、webiopiの動作が速いため、同時に複数のリクエストを捌くことができるようで、HomeKitのシーンで、3つのスイッチを同時に消灯することが可能になりました。

Raspberry pi zero wとhomebridgeとHomeKitの連携その1

だいぶ以前にnodeMcuで4chリレーモジュールのうち、3chを動かし、かつ、物理スイッチからも動かせるようにしました。

gentoolinux.hatenablog.com

しかし、1ヶ月後、WiFiアクセスポイントを増やしたことからなにやら不安定に。

再起動を繰り返す始末。

別なモジュールを買っても同じ。

ESP8266は電源周りがだいぶシビアだそうでして、その影響なのか、exception(29)を吐いてstuckします。

3.3Vのピンに470μFの電解コンデンサをつけたけど、解消されず。

 

そこで、Raspberry Pi zero wに入れ替えた顛末を。

f:id:naoyukinagano:20171121223443j:plain

 

Raspberry piにRaspbian Strechを入れてSSHするところまではいろいろなところに乗っているので割愛。

rootでRaspbian Strechが動かせることを前提に話を進めます。

 

まずは、RaspbianにWebiopiをインストールします。

参考にしたのはこのページ

nw-electric.way-nifty.com

あ、ただ、Python3の有効化をし忘れました(^^ゞ

root@raspberrypi: ~/# mkdir webiopi

root@raspberrypi: ~/# cd webiopi

root@raspberrypi: ~/webiopi# wget https://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz

root@raspberrypi: ~/webiopi# tar xvzf WebIOPi-0.7.1.tar.gz

root@raspberrypi: ~/webiopi# cd WebIOPi-0.7.1

root@raspberrypi: ~/webiopi/WebIOPi-0.7.1# wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch

root@raspberrypi: ~/webiopi/WebIOPi-0.7.1#./setup.sh

んで、Basic認証をハズしました。

root@raspberrypi: ~/# nano /etc/webiopi/config

 /etc/webiopi/configファイルの以下の2箇所をコメントアウト

#passwd-file = /etc/webiopi/passwd

#prompt = "WebIOPi"

で、サービス有効化とスタートです。

root@raspberrypi: ~/# update-rc.d webiopi defaults

root@raspberrypi: ~/#/etc/init.d/webiopi start

 

続いて、PythonでGPIOがGNDに落ちるとトグルスイッチになる(消えてたら付く、付いていたら消える)ようなスクリプトを書きます。

本当ならadd_event_detectを使いたいところなのですが、電位差の検出がシビアすぎて、隣のスイッチまで検出されてコールバックが実行されてしまう始末。

ここはCPUリソースを喰ってでも安定した動作を目指しました。

追記:add_event_detectでは確かに微妙なエッジも検出してしまいますが、検出後のコールバックに"本当に該当スイッチのGPIOが0なのか? を判定するif文を入れれば誤動作を抑止できることがわかりました。詳しくは「その2」へ

 

webiopiからGPIOのピン状況を取得したり、書き込んだりするために、Pythonのrequestsライブラリを利用します。

 root@raspberrypi: ~/# apt-get install python-requests

そして、Rpi.GPIOライブラリ(プリインストール)を活用してスイッチのGPIOがGNDに落ちるのを読み取って、リレーのGPIOの状況を取得し、その状況に応じてGPIOのValueを変化させます。

何度も誤動作しないよう、1度動作したら1秒スリープさせます。

スイッチの読み取りも0.1秒ごとにしました。

プッシュスイッチがGPIO16、リレーがGPIO19で、サインスマートのリレーなので0でON、1でOFFです。

 

追記:これはとても迅速に検出可能ですが、CPUリソースを喰いますのでおすすめしません。

/root/switch/switch1.py

#! /usr/bin/python

import RPi.GPIO as GPIO
import time
import requests
switch = 16
relay = 19
url_front = 'http://localhost:8000/GPIO/'
url_back = '/value'
url_function_out = '/function/out'
url = url_front + str(relay) + url_back
url_function = url_front + str(relay) + url_function_out
url0 = url + '/0'
url1 = url + '/1'

GPIO.setmode(GPIO.BCM)
GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_UP)
gpio_function = requests.post(url_function)

try:
   while True:
   if GPIO.input(switch)==0:
     status = requests.get(url)
     if status.text=='1':
       requests.post(url0)
       time.sleep(1)
     else:
       requests.post(url1)
       time.sleep(1)
   time.sleep(0.1)

except KeyboardInterrupt:
   print '\nswitch.py Stopped'
   GPIO.cleanup()

これらのswitch変数とrelay変数の違いで3ch分をswitch1.py,switch2.py,switch3.pyとして作成しました。

起動時に実行するように、/etc/rc.localに記述します。

# switch read

/root/switch/switch1.py &
/root/switch/switch2.py &
/root/switch/switch3.py &

exit 0

これでスイッチとリレーが連動するようになり、リビングのライトが通常通り使えるようになりました。

次は、homebrigeのconfigを書き換えます。

 

Node.jsのバージョンコンフリクト

Node.jsのバージョンアップなどを行ったあっと、homebridgeを起動すると、次のようなエラーが出て起動できなくなった。

 

 /usr/lib64/node_modules/homebridge/node_modules/mdns/lib/dns_sd.js:35
throw ex;
^

Error: The module '/usr/lib64/node_modules/homebridge/node_modules/mdns/build/Release/dns_sd_bindings.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 48. This version of Node.js requires
NODE_MODULE_VERSION 57. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at Object.Module._extensions..node (module.js:664:18)
at Module.load (module.js:554:32)
at tryModuleLoad (module.js:497:12)
at Function.Module._load (module.js:489:3)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/usr/lib64/node_modules/homebridge/node_modules/mdns/lib/dns_sd.js:24:20)
at Module._compile (module.js:635:30)
at Object.Module._extensions..js (module.js:646:10)
at Module.load (module.js:554:32)

 

どうも、Node.jsのバージョンが、現環境と違うものでコンパイルされているよ。リビルドし直すかインストールし直してね。

と、言っているようです。

インストールし直してもダメだったので、リビルドするのですが、どうやらリビルドコマンドを実行するディレクトリがちゃんとした場所でないと動いてくれないようです。

上記のようにhomebridgeをリビルドするなら、

 

gentoo # cd  /usr/lib64/node_modules/homebridge/

gentoo /usr/lib64/node_modules/homebridge # npm rebuild

 

というように、該当のNode.jsのモジュールがインストールされているディレクトリに移動してからnpm rebuildを実行しましょう。