SyntaxHighlighter

2015年12月14日月曜日

PythonでEnOceanの電文を読み取る

太陽光で動作するEnOceanマグネットセンサSTM429J、温度センサSTM431Jを利用する際のメモ。
Python2.7.9+pySerialで実装。

ESP:EnOcean Serial Protocol

詳しいドキュメントはEnOceanのサイトからDLできる

EnOceanの電文は上記の図(ドキュメントから引用)のようになっている。
  1. Sync. Byte: 同期用の信号で、常に0x55で固定。
  2. header: 4byte固定。2番目のbyteにdataの、3番目にoptional dataの長さが書かれている。
  3. CRC8H: チェックサム。
  4. data: センサIDや動作内容が記載されている。
    • センサid: dataの2~5番目に書かれている4byte。
    • マグネットセンサ(STM429J): dataの6番目のbyteが0x08なら開、0x09なら閉。
    • 温度センサ(STM431J): dataの8番目のbyteを元に算出する。例えば5dなら25.4℃。
      temp = (255.0-val)/255.0*40.0
  5. optional data: 長さはheaderに書かれている。
  6. CRC8D: チェックサム。

Pythonのソースコード

CentOS環境でPython2.7.9/pySerialにより実装した例。
シリアル通信で1byteずつ読み取る。
電文の先頭はかならず0x55になるので、それを起点としてカウントする。
# coding: UTF-8
from serial import *
from sys import exit
from datetime import datetime

port = '/dev/ttyUSB0'

# シリアルポートを開く
try:
    ser = Serial(port, 57600)
    print('open serial port: %s' % port)
except:
    print('cannot open serial port: %s' % port)
    exit(1)

# 初期化
cnt,dataLen,optLen = 0,0,0
telegraph,headList,dataList,optList = [],[],[],[]
ready = True # 電文開始のフラグ管理

# データの解釈とログの記録
while True:
    s = ser.read().encode('hex') # 1byteずつ読み込み
    if s == '55' and ready: # 電文開始
        # 変数のリセット
        cnt,dataLen,optLen = 0,0,0
        telegraph,headList,dataList,optList = [],[],[],[]
        ready = False
        print '=========='
    cnt += 1
    telegraph.append(s)
    if 2 <= cnt <= 5: # header
        headList.append(s)
    if cnt == 5: # header終了, data length取得
        dataLen = int(headList[1],16)
        optLen  = int(headList[2],16)
    if 7 <= cnt <= (6+dataLen): # data
        dataList.append(s)
    if (7+dataLen) <= cnt <= (6+dataLen+optLen): # optional data
        optList.append(s)
    if cnt == (6+dataLen+optLen+1): # 電文終了
        ready = True
        dt = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # ログ出力
        print dt
        print ':'.join(telegraph)
        print 'head...', ':'.join(headList)
        print 'data...', ':'.join(dataList), '(length=%d)' % dataLen
        print 'opt ...', ':'.join(optList),  '(length=%d)' % optLen
        sensorId = ':'.join(dataList[1:5]) # センサID取得
        print 'sid ...', sensorId
        # マグネットセンサ
        if sensorId == '04:00:03:df':
            if   dataList[5] == '08':
                action = 'open'
            elif dataList[5] == '09':
                action = 'close'
            print 'door...', action
        # 温度センサ
        elif sensorId == '04:00:7a:fc':
            val = int(dataList[7],16)
            temp = (255.0-val)/255.0*40.0
            print 'temp...', temp
        # 上記以外のセンサIDは無視
        else:
            continue
出力結果は以下のとおり。
今のところ変な挙動もなく、ドア開閉や気温をきちんと読み取れている。
open serial port: /dev/ttyUSB0
==========
2015-12-14 16:12:58
55:00:07:02:0a:0a:21:04:00:03:df:08:61:01:3a:b3
head... 00:07:02:0a
data... 21:04:00:03:df:08:61 (length=7)
opt ... 01:3a (length=2)
sid ... 04:00:03:df
door... open
==========
2015-12-14 16:13:05
55:00:07:02:0a:0a:21:04:00:03:df:09:66:01:3a:b3
head... 00:07:02:0a
data... 21:04:00:03:df:09:66 (length=7)
opt ... 01:3a (length=2)
sid ... 04:00:03:df
door... close
==========
2015-12-14 16:17:32
55:00:0a:02:0a:9b:22:04:00:7a:fc:00:00:5d:08:3f:01:37:90
head... 00:0a:02:0a
data... 22:04:00:7a:fc:00:00:5d:08:3f (length=10)
opt ... 01:37 (length=2)
sid ... 04:00:7a:fc
temp... 25.4117647059
当初、pySerialのreadline()で1行ずつ受信しようとしたら上手くいかなかった。
面倒でもread()で1byteずつ読み取って、ヘッダ部分からデータ長など読み取りつつ処理する必要がある。

----
2016/6/21追記:プログラムにバグがあるという指摘を受けて修正。Bool型変数readyによる、電文開始フラグに関する処理を追加した。

2015年9月8日火曜日

赤外線センサをArduino経由のシリアル通信で受信する

Arduinoに赤外線センサSE-10を接続し、Raspberry Piで読み取ってみた。

今回使ったもの

  • Arduino UNO
  • 人感センサSE-10
  • 抵抗器:330Ω,10kΩ
  • 赤色LED
  • ピンヘッダ (3pin):SE-10をブレッドボードに繋ぐため
  • Raspberry Pi

配線図

fritzingで配線図を描いてみる。
SE-10側に10kΩ、LEDに330Ωの抵抗を繋ぐ。

ソースコード

Arduino

センサが反応すると1を出力。
反応後5秒間LEDが点灯し、その間は不感時間とする(チャタリング防止のため)。
const int ledPin = 13;
const int pirPin = 2;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if (digitalRead(pirPin) == LOW) {
    digitalWrite(ledPin, HIGH);
    Serial.println("1");
    delay(5000); //不感帯を5秒に設定
  } else {
    digitalWrite(ledPin, LOW);
  }
  delay(100);
}

Python

pySerial
ArduinoのUSBデバイス名は/dev/ttyACM0。これはdmesg等で確認できる。
センサが感知すると、時刻と共に出力される。
# coding: UTF-8
from serial import *
from sys import exit
from datetime import datetime

# ArduinoのUSBデバイス名
port = '/dev/ttyACM0'

# シリアルポートを開く
try:
    ser = Serial(port, 9600)
    print('open port: %s' % port)
except:
    print('cannot open port: %s' % port)
    exit(1)

# 読み出しと出力
while True:
    # 1ライン単位で読み出し、末尾の改行コードを削除
    line = ser.readline().rstrip() 
    try:
        print(datetime.now(),line)
    except Exception as e:
        print(e.message)

参考

秋月電子の焦電型赤外線センサユニット(SE-10)を使う

2015年8月13日木曜日

加速度センサTWE-Lite-2525AのOTA(無線)による設定変更手順

ToCoStickがあれば、加速度センサTWE-Lite-2525Aが検出する動作の種類(1回叩く・2回叩く・落とす・動かす)や感度を無線経由で設定できる。TWE-Lite Rを使えば有線で設定することも可能だが、配線が手間なので無線の方が簡単だ。
ただ、メーカーの説明がややわかりづらいので、作業手順をメモしておく。

用意するもの

  • TeraTermが動作するWindows PC
  • 親機:ToCoStick
  • 子機:TWE-Lite-2525A

設定に必要なソフトウェア(バージョンは投稿時点のもの)

  1. ToCoStick標準アプリケーション
    • App_ToCoStick.exe
    • GUIでセンサの受信を確認できるWindows用アプリケーション
  2. TWE-Liteプログラマ
    • TWE-Programmer.exe
    • ToCoStickのアプリを切り替えるためのWindows用アプリケーション
  3. OTA設定用アプリ
    • Samp_Monitor_EndDevice_Input_JN5164_CNFMST_1_5_3.bin
    • 無線経由でTWE-Lite-2525Aの設定を変更するためのToCoStick用プログラム
  4. 超簡単!TWE標準アプリ
    • App_TweLite_Master_JN5164_TOCOSTICK_1_6_6.bin
    • ToCoStickにデフォルトでインストールされている、センサ受信用プログラム

メーカー情報

OTA設定手順

  1. ToCoStickをPCのUSBポートに挿す
  2. 「TWE-Liteプログラマ」を使ってOTA設定用アプリ(Samp_Monitor_EndDevice_Input_JN5164_CNFMST_1_5_3.bin)をToCoStickへ書き込む
  3. 「TWE-Liteのリセット」ボタンを押す
  4. TeraTermでシリアル接続(設定>シリアルポートからボー・レートを115200に設定)
  5. Enterキーを押すとメニューが出る
    --- CONFIG/Samp_Monitor V1-05-3/SID=xxxxxxxxx/LID=0x00/RC=10000 ---
    a: set Application ID (0x67726305)
    i: set Device ID (--)
    c: set Channels (15)
    x: set Tx Power (13)
    d: set Sleep Dur (500)
    w: set Sensor Wait Dur (0)
    m: set Sensor Mode (0x35)
    p: set Sensor Parameter (16)
    P: set Sensor Parameter2 ()
    k: set Enc Key (0xA5A5A5A5)
    o: set Option Bits (0x00000011)
    ---
    S: save Configuration
    R: reset to Defaults
    *** POWER ON END DEVICE NEAR THIS CONFIGURATOR ***
  6. 例として、動作モードをActive/Inactive(動かした時・静止した時)のみ検出するように設定。 pを押してSensor Parameterを8と入力、SHIFT+sで記録。一瞬だけ下記のようなメッセージが出る。
    !INF FlashWrite Success
    !INF RESET SYSTEM...
  7. Active/Inactiveモードの感度調整。
    大文字のP (Sensor Parameter2) を選んでTHA(動作のしきい値1~15000mg、初期値は2000)とTHI(静止のしきい値1~15000mg、初期値は1938)を指定。
    例えばそれぞれ1000,950ならTHA=1000,THI=950と入力。同様にSHIFT+sで記録。
  8. 設定の入力を終えたら、電池を抜いたTWE-Lite-2525AをToCoStickに近づけ、電池を入れる。すると下記メッセージが出てTWE-Lite-2525Aへの設定が完了。
    !INF REQUEST CONF FR 81020950
    >>> TxCmp Ok(tick=35040,req=#0) <<<
    !INF ACK CONF FR 81020950
    SUCCESS 81020950
  9. ToCoStickに再度「超簡単!TWE標準アプリ(App_TweLite_Master_JN5164_TOCOSTICK_1_6_6.bin)」を書き込み、リセット
あとは前回の記事にしたがってシリアル通信を行えば、指定した動作のみ受信できるようになる。

しかし、手順7に載せた感度調整パラメータを指定すると、何故か動作を認識しなくなってしまう。
理由は不明だが、以下のtweetによればプログラムに問題があるようで、現状ではデフォルトの感度のまま利用するしかなさそうだ。

2015年8月9日日曜日

Raspberry Pi上でTWE-Lite-2525AからのデータをToCoStickを使って受信する

Raspberry PiにToCoStickを繋いで、加速度センサTWE-Lite-2525Aから送られてきた情報をシリアル通信でモニタリングするために行った作業のメモ。
ToCoStickのアプリはデフォルト(超簡単!TWEアプリ)のまま利用。

Raspberry PiのUSBポートにToCoStickを挿し、dmesgを確認。ttyUSB0として認識されたようだ。
user@raspberrypi ~ $ dmesg
(中略)
[  554.996366] usb 1-1.2: Product: TWE-Lite-USB
[  554.996382] usb 1-1.2: Manufacturer: TOCOS
[  554.996398] usb 1-1.2: SerialNumber: XXXXXXXX
[  555.003578] ftdi_sio 1-1.2:1.0: FTDI USB Serial Device converter detected
[  555.003774] usb 1-1.2: Detected FT232RL
[  555.004697] usb 1-1.2: FTDI USB Serial Device converter now attached to ttyUSB0
シリアル通信を行うため、cuコマンドを使えるようにする。
しかし、Raspberry Piの初期ユーザであるpi以外で接続を試みると、権限がないと言われてしまう。
user@raspberrypi ~ $ sudo apt-get install cu
user@raspberrypi ~ $ cu -l /dev/ttyUSB0 -s 115200
cu:  open  (/dev/ttyUSB0):  Permission denied
cu:  /dev/ttyUSB0:  Line in use
どうやら、シリアルポートはdialoutというグループに属していないと一般ユーザはもちろん、rootであっても利用できないようだ(初期ユーザであるpiは登録されているので使える)。なので使用しているユーザを追加してやる。
user@raspberrypi ~ $ sudo vigr
- dialout:x:20:pi
+ dialout:x:20:root,user
すると「整合性を保つために /etc/gshadow を変更する必要があるかもしれません。」とメッセージが出る。なので/etc/gshadowも同じように修正する。
user@raspberrypi ~ $ sudo vim /etc/gshadow
- dialout:x:20:pi
+ dialout:x:20:root,user
一度ログアウトし、再度cuコマンドで接続。無事接続され、TWE-Lite-2525Aを叩いたり動かすとログが出力される。ちなみにシリアル通信を終了させるには~.と入力する。Ctrl+Cでは終了できない。
user@raspberrypi ~ $ cu -s 115200 -l /dev/ttyUSB0
Connected.
:788115017B81020950002F2C000BCD00080F403C3A5E5EDE
:788115018A81020950002F34000BCD00010F3E3E3A5E66C6
:788115018A81020950002F3C000BCD00000F3E3E3A5E56CF
:788115017881020950002F44000BCD00000F3E3E3A5E56D9
~.

Disconnected.
続いて、公式で用意されているPythonによる読み出しスクリプトを動かしてみる。

このスクリプト(twelite_read.py)は引数に指定したポートへ接続し、TWE-Lite標準アプリの出力を整理して表示してくれる。
user@raspberrypi ~/python $ python twelite_read.py /dev/ttyUSB0
open serial port: /dev/ttyUSB0

:788115019081020950002F64000BC500010F3E3E3A5E56A8
  command   = 0x81 (data arrival)
  src       = 0x78
  src long  = 0x81020950
  dst       = 0x00
  pktid     = 0x15
  prtcl ver = 0x01
  LQI       = 144 / -48.10 [dbm]
  time stmp = 189.562 [s]
  relay flg = 0
  volt      = 3013 [mV]
  DI1=1/1  DI2=0/1  DI3=0/1  DI4=0/1
  AD1=1000 AD2=0996 AD3=0932 AD4=1508 [mV]
DI0~4はそれぞれ下記の動作に対応している。上記の例ではDI1が1/1になっているので、TWE-Lite-2525Aを1回叩いたことを意味している。
  • DI1: 軽く1回叩く
  • DI2: 軽く2回叩く
  • DI3: 落とす(50cm~?)
  • DI4: 動かす

2015年7月2日木曜日

scikit-learnで作成した決定木をPDF形式で出力

pythonの機械学習ライブラリscikit-learnで作成した決定木をPDF形式で出力する方法のメモ。
環境はCentOS6.6、Python2.7.9。
決定木を作成するコードは以下のサイトから。

Pythonで決定木を描いてみる - 技術情報棚卸し(平日限定)

上記のサイトでは結果をgraph.dotというファイルに出力し、その後コマンドライン上でPDFへ変換している。これを毎回やるのは面倒なので、python内で処理してしまおうというのが目的。

# encoding: utf-8
import pandas as pd
from sklearn import tree
from sklearn.externals.six import StringIO
from graphviz import Digraph
import pydot

data = pd.read_table('iris.txt')
# 説明変数
variables = ['Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width']
classifier = tree.DecisionTreeClassifier()
# 目的変数はSpecies
classifier = classifier.fit(data[variables], data['Species'])

# 結果をPDFで出力
dot_data = StringIO()
tree.export_graphviz(classifier, out_file=dot_data)
graph = pydot.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf('iris.pdf')

実行してみると、以下のエラーが表示された。
Couldn't import dot_parser, loading of dot files will not be possible.
Traceback (most recent call last):
  File "decision_tree.py", line 18, in 
    graph = pydot.graph_from_dot_data(dot_data.getvalue())
  File "/usr/local/lib/python2.7/site-packages/pydot.py", line 199, in graph_from_dot_data
    return dot_parser.parse_dot_data(data)
NameError: global name 'dot_parser' is not defined

結論だけ書くとpydotをアンイストールしてpydot2を入れることで解決、無事PDFが作成された。
$ sudo pip uninstall pydot
$ sudo pip install pydot2

参考:

2014年7月23日水曜日

推薦システム勉強会で使用した文献

を、まとめておく。

第1回

  • 推薦システムのアルゴリズム
  • 神嶌敏弘, 人工知能学会誌, Vol. 22, No. 6, pp. 826-837, 2007.
  • 人工知能学会誌, Vol. 23, No. 1, pp. 89-103, 2008.
  • 人工知能学会誌, Vol. 23, No. 2, pp. 248-263, 2008.

この分野ではお馴染みのサーベイ論文。基本から幅広く知識を習得できる。3回にわたって人工知能学会誌に掲載されたが、今は著者によりまとめられたものがWeb上に公開されている。

第2-3回


こちらも超有名なサーベイ論文で、10年前のものだが今でも十分通用する内容。推薦精度に関する評価指標を中心に紹介されている。精度以外の指標にも触れているが、確立された手法がなかったため簡単な言及に留めている(今でも確立されていないが)。

第4回


Wikipediaの情報をLOD (Linked Open Data) 形式で公開しているDBpediaを情報源として映画の推薦を行う手法。ジャンルや監督、出演俳優といった属性とその値を用いているところが特徴で、こういった内容ベース的なアプローチと構造化された情報を扱うDBpediaは相性が良い。

第5回


Amazonに投稿されているレビューから,カメラであれば重量や価格といった観点(=属性)に対する嗜好を抽出する。この既存ユーザの嗜好情報を用いて、完全に把握できていない新規ユーザの嗜好 (partial preference) の欠損値を補う手法。レビューの選択基準が不明瞭だが、新規ユーザに対する推薦精度改善手法として面白いアプローチ。

第6回


ユーザが好む(高く評価する)可能性が高いアイテムを推薦する、というのがほとんどの推薦システムにおいて目的となっている。その中で、この手法はサービス提供者(主に企業)の利益を最大化することを目的としている。価格(利益)の高い商品や、キャンペーンや在庫処分などの理由でとある商品を優先的に推薦する、というのは企業では既にある程度やっていることだとは思う。ただ、アカデミックな分野ではあまりこういった目的での手法は見られなかったので、面白い手法と思う。

第7回


少し古い文献だが、推薦時に理由(なぜそのアイテムが推薦されたが)を併せて提示することでユーザの満足度向上を試みた手法。提案手法は3つあるが、そのなかのNeighbor Style Explanation(協調フィルタリングによる近傍ユーザの評価値分布を推薦理由としてユーザに提示する手法)において、ユーザがその評価に影響され推薦アイテムに対して高い評価をしがちであるという結果。この結果に対して、推薦理由の提示により高い評価に影響を受ける=推薦アイテムを過大評価することに繋がり満足度が低下する、という考察が興味深い。

第8回


ホテルに対するレビューを対象として、属性(立地、サービス、部屋など)への言及の度合いを自然言語処理により推定し、コンテキストベースの推薦に用いている手法。新規ユーザやアイテムに対して推薦精度が低下するcold-start問題への解決策として、コンテキストベースの推薦を行う手法は広く行われている。新規ユーザに対して特に有効なアプローチだが、いずれの手法もアイテムに対する情報が事前に十分得られていることが前提となっている。

第9回


いわゆる「ロングテール」に着目し、不人気アイテムを推薦することに重きを置いた手法。タイトルにAccuracyとあるが精度の検証はせず、アイテムの人気度を考慮した再現率を評価指標としている。着眼点は面白いが、データセットを用いたオフライン実験で検証できる範囲は限られており、そのあたり限界を感じる。