雑食エンジニアの気まぐれレシピ

日ごろ身に着けた技術や見知った知識などの備忘録的なまとめ.主にRaspberry Piやマイコンを使った電子工作について綴っていく予定.機械学習についても書けるといいな.

レオリモコンとアレクサを連携させる[レオリモコン操作編]

前回の概要編に引き続き,レオリモコンとアレクサの連携についてです.今回はいきなり最終端のレオリモコン操作部分について書きたいと思います.アレクサに出した指示は,めぐりめぐってこの部分を実行することになります.

概要編で触れたとおり,レオリモコンはIRemoconWifiと同様にtelnet経由で操作することができます.IRemoconはレオリモコンに比べればずっとメジャーなので,すでに実装されている方はちらほらいるみたいです.
node-iRemocon つくった - 凹みTips
様のjava script版など.
正直この完成度を超えられる気はしないのですが,勉強も兼ねて使い慣れたPythonで書いてみました.

import telnetlib
import traceback

class LeoRemoconController:
    def __init__(self,targetIP,port=51013):
        self.targetIP=targetIP
        self.__PORT=port

    def testConnection(self):
        rets=self.sendCommand("*au")
        if(rets[0] != "ok"):
            raise ValueError(rets)

    def sendCommand(self,cmd,*values):
        self.tn.write(cmd.encode('ascii'))
        for value in values:
            self.tn.write(b';' + value.encode("ascii"))
        self.tn.write(b"\r\n")

        timeout=1
        if cmd=="*ic":
            timeout=60

        result,values=self.waitUntilReturn(timeout=timeout)

        #if cc command succeed, returns previous command(ic)'s result.
        #So read and waste it.
        if cmd=="*cc" and ret[0]=="ok":
            self.waitUntilReturn()

        return result,values

    def waitUntilReturn(self,timeout=1):
        retTupple = self.tn.expect([b'(ok.*|err.*)\r\n'],timeout)
        if(retTupple[0] == -1):
            raise ConnectionError()
        ret = retTupple[1].group(1).decode('ascii')
        result,*values=ret.split(";")
        return result,values

    def openTelnet(self):
        self.tn = telnetlib.Telnet(self.targetIP,self.__PORT)

    def closeTelnet(self):
        self.tn.close()

    #executes at start of "with" syntax
    def __enter__(self):
        try:
            self.openTelnet()
            self.testConnection()
        except:
            traceback.print_exc()
            self.closeTelnet()
            raise ConnectionError()
        return self

    #executes at end of "with" syntax
    def __exit__(self,type,balue,traceback):
        self.closeTelnet()

で,これを次のように呼んで使います.

if __name__ == "__main__":
    print("test LeoRemocon")

    ip="10.151.224.48" #set your LeoRemocon's IP address

    with LeoRemoconController(ip) as lrc:
        result,values = lrc.sendCommand("*ic","lightOn") #set command
        print(result)

with構文でtelnetのopenとcloseを管理しているのが地味なこだわりポイントですね.
レオリモコンへのアクセスはローカルセグメント内に限定されているため基本タイムアウトを1秒に設定していますが,icコマンド(信号の登録)のみ60秒に設定しています.またレオリモコンからの応答はリストにして返しているのですが,ccコマンドだけ応答の形が異なるためif文で逃がしています.この辺りはゴミコード感がすごいですね.
エラー番号の意味についてはマニュアルを参照するしかありませんが,先の
node-iRemocon つくった - 凹みTips
様のコードの中にエラーコードとエラー内容の対応mapがありますので,これを使わせていただくとエラー内容の取得がはかどりそうです.ありがとうございます.

このままでは使いにくいので,レオリモコンへの信号の登録や実行などをブラウザからできるようにWebページを作ろうとは思っているのですが,Webについてはど素人なのでそこそこ時間がかかりそうです.そちらについては形になり次第また書きたいと思います.