EnOceanの環境発電型ドア開閉センサー（STM250J）の情報をPythonで拾い、内容をESP3に従い解釈する。

#動機・目的
##目的
EnOceanの環境発電型ドア開閉センサー（STM250J）の情報をラズパイで拾って、拾ったデータをESP3、ERP2に従い解釈をする。

##動機
「環境発電」という単語を最近耳にすることが多くなってきたので、環境発電を利用して何かサービスめいたものが作成できないか検討する途中で、EnOceanの製品をご紹介いただいので実際に使ってみようと思い立った。
#環境
ラズパイ：Raspberry Pi 3 Model B
OS：Raspbian 9
電文受信：USB400J（USB受信ドングル）
センサー：STM250J（ドア開閉センサー）

ラズパイにUSB400Jを刺してセンサーの情報を拾います。Raspbianをインストールしネットワーク設定が完了した後、各パッケージのアップデート完了後から作業を開始しています。ラズパイに入っているPythonのバージョンは2.7.13でした。

物は[こちら](http://shop.ermine.co.jp/shopdetail/000000000046/)で購入させていただきました。

#実施内容
##Pythonで受信した情報を解釈する


以下のソースでUSB400Jの受信している情報を確認し解釈します。
飛んできているデータはEnOcean Serial Protocol 3 (ESP3)というプロトコルに従ったものが飛んでくるので、仕様に従い解釈します。

```USB400Jtest2.py
# coding: UTF-8

import serial
import binascii
import threading

class Esp3:
    data_length = 0
    data = bytearray(0)
    option_length = 0
    option = bytearray(0)
    packet_type = b'\x00'
    crc8_header = b'\x00'
    crc8_data = b'\x00'

    def print_data(self):
        print('data_length:%s' % self.data_length)
        print('data:%s' % binascii.hexlify(self.data))
        print('option length:%s' % self.option_length)
        print('option:%s' % binascii.hexlify(self.option))
        print('packet type:%s' % binascii.hexlify(self.packet_type))
        print('crc8 header:%s' % binascii.hexlify(self.crc8_header))
        print('crc8 data:%s' % binascii.hexlify(self.crc8_data))

class Erp2Thread(threading.Thread):

    def __init__(self, esp3):
        super(Erp2Thread, self).__init__()
        self.esp3 = esp3

    def run(self):

        sub_telegram = self.esp3.option[0] #オプションからSub Telegramを取り出す。Send: 3 / receive: 1
        dbm = self.esp3.option[1] * (-1) #オプションからdBmを取り出す。正負反転させる

        erp2_header = self.esp3.data[0] #データからヘッダーとなる1バイトを取り出す

        if erp2_header == ord(b'\x21'): #Originator-ID 32 bit nio Destination-ID、No extended header、1BS telegram のものだけ処理する
            originator_id = binascii.hexlify(self.esp3.data[1:5]) #データからセンサーIDを取り出す
            status_code = self.esp3.data[5] #データからセンサーのステータスを取り出す
            button_status = ""
            door_status = ""


            if status_code == ord(b'\x09'):  #センサーのステータスを人間が解る言葉に翻訳する。STM250Jはcloseとopenだけだが、ボタンのセンサーも同様のプロトコルらしい
                button_status = "押されていません。"
                door_status = "閉まっています。"
            elif status_code == ord(b'\x08'):
                button_status = "押されていません。"
                door_status = "開いています。"
            elif status_code == ord(b'\x01'):
                button_status = "押されています。"
                door_status = "閉まっています。"
            elif status_code == ord(b'\x00'):
                button_status = "押されています。"
                door_status = "開いています。"

            print( 'センサーID：%sは%s' % (originator_id , door_status) )

def main():
    ser = serial.Serial('/dev/ttyUSB0',57600,timeout = 1)

    sync_start = False
    while True:

        denbun = ser.read()
        esp3 = Esp3()

        if sync_start or denbun == b'\x55':   #0x55で同期開始
            sync_start = True

            denbun = ser.read()    #データ長を読み込み
            data_length = 256 * ord(denbun)
            denbun = ser.read()
            data_length = data_length + ord(denbun)

            esp3.data_length = data_length

            denbun = ser.read()   #オプション長を読み込み
            option_length = ord(denbun)

            esp3.option_length = option_length

            denbun = ser.read()  #パケットタイプを読み込み
            packet_type = denbun

            esp3.packet_type = packet_type

            denbun = ser.read() #CRC8(Header)を読み込み
            crc8_header = denbun

            esp3.crc8_header = crc8_header

            data = bytearray(data_length)
            for loop_counter in range(data_length):
                denbun = ser.read() #データを読み込む、長さ分読み込む
                data[loop_counter ] = denbun

            esp3.data = data

            option = bytearray(option_length)
            for loop_counter in range(option_length):
                denbun = ser.read() #オプションんを読み込む、長さ分読み込む
                option[loop_counter] = denbun

            esp3.option = option

            denbun = ser.read() #CRC8(Data)を読み込み
            crc8_data = denbun

            esp3.crc8_data = crc8_data

            sync_start = False

        if esp3.packet_type == b'\x0A':  #パケットタイプがERP2のものだけスレッドで処理する

            erp2_thread = Erp2Thread(esp3)

            erp2_thread.start()

main()
```

実行結果は以下の通りです。センサーとマグネットを近づけたり遠ざけたりすると表示されます。同時接続も可能です。

```sh
センサーID：0400e559は閉まっています。
センサーID：0400e559は開いています。
センサーID：0400e559は閉まっています。
センサーID：0400e559は開いています。
センサーID：0400e6aeは閉まっています。
センサーID：0400e563は閉まっています。
センサーID：0400e559は閉まっています。
センサーID：0400e6aeは開いています。
センサーID：0400e563は開いています。
センサーID：0400e559は開いています。
センサーID：0400e559は閉まっています。
センサーID：0400e6aeは閉まっています。
センサーID：0400e6aeは開いています。
センサーID：0400e559は開いています。
センサーID：0400e6aeは閉まっています。
センサーID：0400e563は閉まっています。
センサーID：0400e559は閉まっています。
センサーID：0400e559は開いています。
センサーID：0400e563は開いています。
センサーID：0400e6aeは開いています。
センサーID：0400e6aeは閉まっています。
センサーID：0400e563は閉まっています。
センサーID：0400e6aeは開いています。
センサーID：0400e563は開いています。
センサーID：0400e563は閉まっています。
センサーID：0400e6aeは閉まっています。
センサーID：0400e559は閉まっています。
センサーID：0400e6aeは開いています。
センサーID：0400e563は開いています。
センサーID：0400e559は開いています。
```
#解説
EnOcean系の製品は複数の仕様にまたがって作られています。
階層的なイメージでとらえていただくと解りやすいかもしれません。一番上のプロトコルがESP3になっています。
今回使用したSTM250JというセンサーはERP2(EnOcean Radio Protocol 2)という下位プロトコルを使用しているので、パケットタイプを識別してERP2のデータだけを処理しています。
センサーが送ってくるデータの実装はEEP(EnOcean Equipment Profiles)という仕様に基づき実装されているので、ERP2で受信したデータをEEPに基づき解釈しています。

解釈のイメージを図で表すとこのようになります。![WS000046.JPG](https://qiita-image-store.s3.amazonaws.com/0/168941/7c5ae7c9-972c-2ef2-6489-498bfb9b18fe.jpeg)

詳細は仕様は公式のドキュメントをご参照ください。

#参照ドキュメント
[ESP3](https://www.enocean.com/fileadmin/redaktion/pdf/tec_docs/EnOceanSerialProtocol3.pdf)の仕様
[ERP2](https://www.enocean.com/fileadmin/redaktion/pdf/tec_docs/EnOceanRadioProtocol2.pdf)の仕様
[EEP](https://www.enocean.com/fileadmin/redaktion/enocean_alliance/pdf/EnOcean_Equipment_Profiles_EEP_V2.6.3_public.pdf)の仕様

#感想・今後
想像以上に長いソースになってしまいました。あと仕様を理解するのに時間がかかりました。
センサーのステータスは取得できるようになったので、あとはサービスを提供するサーバへデータを飛ばしていきます。