目次
- はじめに
- 仕組み
- デジタルフォトフレーム(Sony DPF-HD800)
- WiFi_shareable_SDcardReader
- Homebridge
- Homebridge-cmd4
- Pythonスクリプト
- 画像の用意
- 完成したもの
- おわりに
はじめに
以前に
Sonyのデジタルフォトフレーム(DPF-HD800 DPF-D92)を分解してみた
という記事を書きましたが、このときに在室表示板にしてみたいという話をしましたが、それの続きです。このデジタルフォトフレームには全くWiFiがついていないので、それが課題でした。
仕組み
大まかな構成図は以下の感じです。
WiFi_shareable_SDcardReaderとそのAPI、Homebridge-cmd4あたりがこのプロジェクトの中核部分です。
デジタルフォトフレーム(Sony DPF-HD800)
詳しくは、
Sonyのデジタルフォトフレーム(DPF-HD800 DPF-D92)を分解してみた
で書いてあります。公式の主な仕様
2013年発売のWiFi機能がない、シンプルなデジタルフォトフレームです。
入力端子は、SDカードとメモリースティックとUSB(ホスト、デバイス両方)です。今回はUSB(ホスト)を使用します。
WiFi_shareable_SDcardReader
詳しくは、
WiFi経由のブラウザと同時アクセスもできるUSB SDカードリーダ(WiFi_shareable_SDcardReader)をESP32 S3・S2・Raspberry Pi Pico Wで作ってみた
で書いてあります。
USBメモリと振る舞いつつ、WiFiからも内容を変更できるものです。ESP32 S3で作成しました。これをデジタルフォトフレームに刺して、サーバからUSBの内容を書き換える感じです。
設定としては、USB_REFRASH、REFRESH_TIME_LENGTH 10000としました。
Homebridge
言わずもがな知れた、あらとあらゆるIoT機器をHomekitに対応化させることができるソフトウェアです。プラグインをいれることで様々な方法でデバイスを追加できます。
今回はDebian上で動かしています。
Homebridge-cmd4
Homebridgeのプラグインで、任意のコマンドに引数をつけて実行して、標準出力の結果と戻り値を利用してデバイスを操作するものです。詳しくは、
Qiita @n1330「homebridge-cmd4 設定例」https://qiita.com/n1330/items/f5ede320e9b5963bccd1
が参考になります。任意のデバイスの種類に対応していて、非常に使いやすいです。CMD4 Portalというデバイスやパラメータの一覧がまとめられているサイトがあるので楽です。昔に一度使った、homebridge http advanced accessoryを使おうと思ったのですが、難しかったのでやめました。
今回は、Televisonを使用して、入力切替のところで、表示の切り替えができるようにしました。
Homebridgeのconfig.jsonはこんな感じです。入力切替先の数だけInputSourceを増やさないといけないため、かなり長くなってしまいました。
,
{
"platform": "Cmd4",
"name": "Cmd4",
"accessories": [
{
"type": "Television",
"configuredName": "在室表示板",
"displayName": "Occupancy_Sign",
"active": "ACTIVE",
"activeIdentifier": 1,
"sleepDiscoveryMode": "ALWAYS_DISCOVERABLE",
"remoteKey": "SELECT",
"publishExternally": true,
"category": "TELEVISION",
"linkedTypes": [
{
"type": "InputSource",
"configuredName": "在室",
"displayName": "Input1",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 1,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "不在",
"displayName": "Input2",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 2,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "調整中",
"displayName": "Input3",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 3,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "部屋外",
"displayName": "Input4",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 4,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "外出中",
"displayName": "Input5",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 5,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "就寝中",
"displayName": "Input6",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 6,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "会議中",
"displayName": "Input7",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 7,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "バイト中",
"displayName": "Input8",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 8,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "取込中",
"displayName": "Input9",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 9,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
},
{
"type": "InputSource",
"configuredName": "実験中",
"displayName": "Input10",
"currentVisibilityState": "SHOWN",
"inputSourceType": "USB",
"isConfigured": "CONFIGURED",
"identifier": 10,
"targetVisibilityState": "SHOWN",
"polling": true,
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
}
],
"polling": [
{
"characteristic": "active",
"interval": 60,
"timeout": 60000
},
{
"characteristic": "remoteKey"
},
{
"characteristic": "activeIdentifier"
}
],
"state_cmd": "python3 -u /home/unagidojyou/occupancy_sign/occupancy_sign.py"
}
]
}
state_cmdが引数を付けて実行されるコマンドです。今回は、Pythonスクリプトを実行するので、長めになっています。Pythonに渡される引数はGetコマンドのときは、
Get 'Input1' 'CurrentVisibilityState'
Get 'Occupancy_Sign' 'Active'
Get 'Occupancy_Sign' 'RemoteKey'
みたいな感じです。Get "DisplayName" "内容"
っぽいです。
Setコマンドのときは、
Set Occupancy_Sign Active 1
Set Occupancy_Sign RmoteKey 8
Set Occupancy_Sign ActiveIdentifier 1
みたいな感じです。Set "DisplayName" "項目" "数字"
っぽいです。
Pythonスクリプト
Homebridge-cmd4から呼び出されるスクリプト自体はこんな感じです。WiFi_shareable_SDcardReaderのフロントエンドはArduinoIDE_SD_FAT32_Fileserverと全く同様なので、そのAPI(ArduinoIDE_SD_FAT32_Fileserver_API.py)を使用しています。
Getコマンドに関して
・InputのCurrentVisibilityStateは、常に標準出力に0を返します。
・Occupancy_SignのRemoteKeyは、リモコンのボタンに関するものですが、リモコンは存在しないので常にディフォルト値の8を返します。
・Occupancy_SignのActiveは、ルートディレクトリが存在すれば、1(ON)を返し、何かしら失敗した場合は0(OFF)を返します。
Setコマンドに関して、常にInputに対してSetはないので常にOccupancy_Signに関するものです。
・Activeは、常に電源がONでON/OFFは制御できないのでGetのActiveと同様な動作をします。
・RemoteKeyも、Getと同様で常に8で、何も行いません。
・ActiveIdentifierは、入力切替に関することなので、対応した番号の写真に切り替わるようにします。
各種操作に失敗した場合は、sys.exit(-1)でHomebridge-cmd4にエラーを知らせます。
画像を切り替える仕組みですが、SDカードにすべての画像ファイルを入れて、表示させたい画像のみ、拡張子をjpgにし、それ以外の画像の拡張子をdjpgにしています。名前の変更だけなので、いちいち、画像をアップロードしたり削除する手間がないため通信量を抑えることができます。
import ArduinoIDE_SD_FAT32_Fileserver_API as SD_API
import sys
import os
def get_active():
if SD_API.check_path_exist('/'):
return 1
else:
return 0
def get_remoteKey():
return 8
def get_activeIdentifier():
root_dir = SD_API.get_file_dir('/')
if root_dir == False:
print('could not get dir list', file=sys.stderr)
return 0 #エラー時
for filename in root_dir:
if not filename[-1] == '/':
if os.path.splitext(os.path.basename(filename))[1] == '.jpg':
return jpg_older.index(os.path.splitext(os.path.basename(filename))[0])
print('could not find .jpg file', file=sys.stderr)
return 0 #jpgファイルが見つからないとき
def set_active(state):
return get_active()
def set_remoteKey(state):
return get_remoteKey()
def set_activeIdentifier(state):
root_dir = SD_API.get_file_dir('/')
if root_dir == False:
print('could not get dir list', file=sys.stderr)
return 0 #エラー時
for filename in root_dir:
if not filename[-1] == '/':
file_ext = os.path.splitext(os.path.basename(filename))[1]
file_name = os.path.splitext(os.path.basename(filename))[0]
if file_ext == '.jpg' or file_ext == '.djpg':
if (file_name == jpg_older[state]) and (file_ext == '.djpg'): # djpg->jpg
if not SD_API.change_name('/' + filename, '/' + file_name + '.jpg'):
print('could not change ' + filename, file=sys.stderr)
return 0
elif (file_ext == '.jpg') and (not file_name == jpg_older[state]):
if not SD_API.change_name('/' + filename, '/' + file_name + '.djpg'):
print('could not change ' + filename, file=sys.stderr)
return 0
return get_activeIdentifier()
args = sys.argv
if len(args) < 1:
print('arg is required')
request = args[1]
if request == 'Get' and (not len(args) == 4):
print('worong args', file=sys.stderr)
sys.exit(-1)
elif request == 'Set' and (not len(args) == 5):
print('worng args', file=sys.stderr)
sys.exit(-1)
name = args[2]
characteristic = args[3]
if request == 'Set':
state = int(args[4])
SD_API.set_ipaddr('192.168.0.0')
# homebridgeのidentifierと番号を揃える。ファイル名と同じものを入力
jpg_older = ['', '在室', '不在', '調整中', '部屋外', '外出中', '就寝中', '会議中', 'バイト中', '取込中', '実験中']
activeIdentifier = 1
if request == 'Get':
if name == 'Occupancy_Sign':
if characteristic == 'Active':
print(get_active())
elif characteristic == 'RemoteKey':
print(get_remoteKey())
elif characteristic == 'ActiveIdentifier':
print(get_activeIdentifier())
elif name[:5] == 'Input':
print(0)
sys.exit(0)
elif request == 'Set':
if characteristic == 'Active':
print(set_active(state))
elif characteristic == 'RemoteKey':
print(set_remoteKey(state))
elif characteristic == 'ActiveIdentifier':
print(set_activeIdentifier(state))
sys.exit(0)
jpg_olderがファイル名と、Homebridge-cmd4のconfig.jsonに記した、”identifier”を結びつけます。jpg_older[“identifier”]がjpgファイルの名前と一致する必要があります。今回は日本語のファイル名なので、上のコードの様になっています。
画像の用意
画像はパワーポイントで作成しました。DPF-HD800の解像度が800×480で、ピクセル数に0.0264を掛けたものがパワーポイントのスライドのサイズ(cm)なようなので、幅を21.118cm、高さを12.674cmに設定しました。CubePDFとかを使ってJPEGに変換しました。Pythonスクリプトのjpg_olderに合わせて日本語なファイル名で保存しました。
完成したもの
WiFiの付いてない古いフォトフレームをHomeKitで使える、在室表示板にした!
— Unagi Dojyou (@Unagi_Dojyou) November 9, 2024
WiFi_shareable_SDcardReaderと、ArduinoIDE_SD FAT32_FileserverのAPIと、Homebridges-cmd4の組み合わせ pic.twitter.com/6q3m8iOmaw
おわりに
作ってから2週間以上経ちますが問題なく動いていていい感じです。なかなか便利です。
今回のプロジェクトで一番大変だったのは、Python APIの作成です。直接は関係ないので、この記事には書いていないのですが、かなり時間がかかりました。結果的にかなり信頼性の高いArduinoIDE_SD_FAT32_Fileserver_API.pyが書けたので良かったです。