スマートホーム構築記

IoTの恩恵を受けたいけど家庭用はなかなか出てこない。なら作ろうというブログ

Raspberry Pi3 でのlirc信号生成スクリプトの作成

目的

前回信号解析を行ったリモコンの信号を解析結果に基づいて生成することを目指します。

方針

システムは以下の方針で設計します。

  1. 今後、他のリモコンも登録して使う可能性があるので汎用的なシステムとする
  2. 設定入力は人間が理解できるフォーマットにする
    1. 将来的にはGUIによって設定可能なものとする
  3. 出力はlircのフォーマット

システム構成

上記の方針と自分の勉強もかねて以下のシステム構成にしました。
f:id:kagemomiji:20161002123347p:plain

設定はjson形式で入力し、内部には機械の設定情報と使用する信号フォーマットのIDを格納します。
コンバートスクリプトはこのフォーマットIDから信号フォーマットライブラリを参照して、標準出力にlirc用の信号を出力します。
信号フォーマットライブラリを拡張することでコンバートスクリプトを大幅に変更することなく信号が出力できるようになる仕組みです。
json形式にしたのはwebアプリケーションとの親和性が高そうなことと、自分の勉強のためです。

詳細

次にそれぞれの詳細を説明していきます。

設定(json)

 {
        "format_id":"a01",
        "data":{
                "mode":"d",
                "status":"on",
                "temperature":28,
                "strength":"auto",
                "direction":"auto",
                "dry":"off",
                "power":"off",
                "silent":"off",
                "smell":"on"
        }
}

大きく分けて二つのオブジェクトからなります。
使用するフォーマットIDと設定値格納オブジェクトです。
フォーマットIDはこの後に'.json'をつけると信号フォーマットライブラリのフォーマットファイルになります。
設定値格納オブジェクトは運転モードや温度などの設定情報をさらにネストして格納しています。

信号フォーマットファイル(json)

今回解析したエアコンの信号をこのようなjson形式として書きました。

{
        "id":1,
        "type":"AEHA",
        "target":"aircon",
        "signal":{
                "T":436,
                "trailer_t":10000,
                "frame":[
                                {
                                        "bitmap":[
                                                        [0],
                                                        [0],
                                                        [0,4],
                                                        [0],
                                                        [0],
                                                        [4,0],
                                                        [1],
                                                        [0],
                                                        [4,0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [6,5,0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0]
                                        ],
                                        "value":[
                                                "0x2",
                                                "0x20",
                                                "0x0+0xe",
                                                "0x4",
                                                "0x0",
                                                "mode+status",
                                                "temperature",
                                                "0x80",
                                                "strength+direction",
                                                "0x0",
                                                "0x0",
                                                "0x6",
                                                "0x60",
                                                "dry+silent+power",
                                                "0x0",
                                                "0x80",
                                                "0x0",
                                                "smell",
                                                "sum"
                                        ]
                                },
                                {
                                        "bitmap":[
                                                        [0],
                                                        [0],
                                                        [0,4],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0]
                                        ],
                                        "value":[

                                               "0x2",
                                                "0x20",
                                                "0x0+0xe",
                                                "0x4",
                                                "0x0",
                                                "0x0",
                                                "0x0",
                                                "0x6"
                                        ]
                                }
                ],
                "rule":{
                        "mode":{
                                "a":"0x0",
                                "h":"0x4",
                                "c":"0x3",
                                "d":"0x2"
                        },
                        "status":{
                                "off":"0x0",
                                "on":"0x1",
                                "toff":"0x5",
                                "ton":"0x8"
                        },
                        "strength":{
                                "auto":"0xa",
                                "1":"0x3",
                                "2":"0x4",
                                "3":"0x5",
                                "4":"0x7"
                        },
                        "direction":{
                                "auto":"0xf",
                                "1":"0x1",
                                "2":"0x2",
                                "3":"0x3",
                                "4":"0x4",
                                "5":"0x5"
                        },
                        "smell":{
                                "on":"0x16",
                                "off":"0x6"
                        },
                        "dry":{
                                "on":"0x1",
                                "off":"0x0"
                        },
                        "silent":{
                                "on":"0x1",
                                "off":"0x0"
                        },
                        "power":{
                                "on":"0x1",
                                "off":"0x0"
                        }

                }
        }
}

大雑把には以下のような構成です。
f:id:kagemomiji:20161002131256p:plain:w500
header部ではこのファイル自体の情報を定義しています。
次に信号設定部で信号における必要情報を定義しておきます(単位時間Tなど)
次に出力されるフレームを1フレーム毎にビットシフト量の定義である"bitmap"と格納する値の定義である"value"をセットにして配列として格納します。
基本的な考え方として8bit(1バイト)を一単位として考えその中でどのようなデータがどのようなビットシフトがなされて格納されているのかといった情報をフレーム毎に持っているというものです。
バイトが固定値であればその値を、そうでなければ内容について"value"に定義します。上位4bitと下位4bitで意味が異なる場合は"+"で上位ビットから順番に連結して表現します。
また、設定ファイルから設定値を反映するためのルールを"rule"オブジェクトとして格納します。人間が理解できる値から信号化するための16進数変換するルールをここに定義します。

コンバートスクリプト

次にコンバートスクリプトになります。
機能としては

  1. 設定jsonの読み込み
  2. 信号フォーマットjsonの読み込み
  3. 信号を各バイトごとに10進数化して生成
  4. 信号を2進数化およびLSB,MSB対応
  5. 2進数化された信号をpulse,space記法に変換
  6. 結果を標準出力

の6個の機能からなります。

# coding:utf-8

#import mod
import json
import sys
import rev_bit

依存関係は以上のようになります。rev_bitは前回に作成したMSB/LSBの反転スクリプトです。

#check argvs
argvs = sys.argv
argc = len(argvs)
if(argc !=2):
        print 'Usage: #python %s filename' % argvs[0]
        quit()
#set local value
SIGNAL_LIB_DIR = "/home/pi/system/settings/format/"

初期化処理です。引数チェックと信号ライブラリのディレクトリを設定しています。
ディレクトリ指定は絶対パススクリプトを置いたところからの相対ディレクトリです。

##################################
#load setting
##################################
try:
        s = open(argvs[1],'r')
        set_data = json.load(s)
        data = set_data["data"]
except ValueError:
        print 'setting json format error'
        s.close()
        quit()
except IOError:
        print 'No Such File or Directory %s' %(argvs[1])
        quit()
except NameError:
        print 'Unknown format of setting %s' %(argvs[1])
        s.close()
        quit()
except :
        print 'Unexpected error:',sys.exc_info()[0]
        quit()

引数で指定された設定ファイルを読み込み処理です。例外処理を入れていますが基本的にいれなくてもよいと思います。今回少し私が勉強したことを試す意味で入れています。

##################################
#load format
##################################
format_file = set_data["format_id"] + ".json"
f = open(SIGNAL_LIB_DIR + format_file,'r')
format = json.load(f)

信号フォーマットファイルの読み込みです。設定ファイルに格納された["format_id"]+'.json'のファイルを初期化時に指定した"SIGNAL_LIB_DIR"内からロードするといった処理になります。

##################################
#convert setting to binary signal
##################################


#initialize
signal = format["signal"]
rule = signal["rule"]
format_type = format["type"]
bin_signal = ''

#convert setting to binary signal
for frame in signal["frame"]:
        bin_signal = bin_signal+'L'
        bitmap = frame["bitmap"]
        raw_signal = []
        for i in xrange(len(bitmap)):
                val = frame["value"][i].split("+")
                sig = 0
                for j in xrange(len(val)):
                        #append fixed_bit
                        if '0x' in val[j]:
                                sig = sig + int(val[j],16) << bitmap[i][j]
                        #append setting_bit
                        elif val[j] in rule.keys():
                                sig = sig + int(rule[val[j]][data[val[j]]],16)<<bitmap[i][j]
                        #append error_check_bit
                        elif val[j] == 'sum':
                                sig = reduce(lambda x,y:x+y,raw_signal) & 0x0ff
                        else:
                                sig = sig + data[val[j]] << bitmap[i][j]

                #append new signal
                raw_signal.append(sig)
                bin_signal = bin_signal + bin(rev_bit.reverse_bit8(sig)).replace('0b','').zfill(8)

        if format_type == 'AEHA':
                #append tracer tag
                bin_signal = bin_signal + 'T'

'bin_signal'にLeaderは'L',信号は'0'or'1',Trailerは'T'として格納していきます。

バイトごとのデータをsigに格納し、これを変換していくことで実現しています。
sigの計算は大きく3つあります.

  1. 固定値をビットシフトして追加
  2. 設定値を読みこんでビットシフトして追加
  3. エラー処理用の信号を処理して追加

1つ目は固定値の追加です。1バイトすべてが固定値の場合はvalue='0x16',bitmap=[0]という組み合わせになっていて0x16を左側0bitシフトして追加という処理になります。
Data0とParityコードは4bitずつの固定値となっているので以下のような組み合わせで記述されていますvalue='0x0+0xe',bitmap=[0,4]。これは固定値をそれぞれ順番に"+"で表現し、それらをいくつ左側にbitシフトするかといったようにbitmapを記述しています。
これは設定値を反映する場合にも使用されています。たとえば"mode+status"のように1バイトに運転モードと電源ステータスが共存する場合にも同じように"+"で結合して記述します。その後,ruleオブジェクト内に記載された設定値変換ルールによって置換してbitmap=[4,0]に従ってそれぞれビットシフトして加算されます。
最後にエラー処理用のバイトは今回のエアコンのリモコン信号がすべてのバイト信号の総和で定義されていたのでその処理を実装しています。今後別の信号を扱う際に拡張する予定です。
最後にこのsigをMSB/LSBのため並び順を逆順に変換して、8ケタの2進数文字列化を行って結合していきます。
リモコン信号は下の桁から順番に1桁目,2桁目,3桁目…,8桁目と流れていますが、プログラム上では下の桁が最後にくるように8桁目,7桁目,...2桁目,1桁目とならんでいるからです。

###################################
#convert binary signal to ir_signal
###################################

#initialize
T = signal["T"]
ir_signal = ''

#AEHA format setting
if format_type == 'AEHA':
        trailer_t = signal["trailer_t"]

        for i in xrange(len(bin_signal)):
                #append 0
                if bin_signal[i] == '0':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(T) + '\n'
                #append 1
                elif bin_signal[i] == '1':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(3*T)+ '\n'
                #append Leader
                elif bin_signal[i] == 'L':
                        ir_signal = ir_signal + 'pulse ' + str(8*T) + '\nspace ' + str(4*T)+ '\n'
                #append Trailer
                elif bin_signal[i] == 'T':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(trailer_t) + '\n'
print ir_signal

最後はAEHA,NEC,sonyフォーマット毎にそれらに従ったpulse,space信号に変換していきます。
置換で生成せずわざわざfor文で順番に処理するのは変換した文字列に0,1が生成された場合に誤って変換されるのを防ぐためです。
最後にprint文を用いて生成した文字列を標準出力に出力しています。

ソースコード

以下にjson2python.pyとテスト用のjsonをlibに格納していますのでどうぞご自由にご利用ください。
github.com