日別アーカイブ: 2014年5月30日

Raspberry Pi と IP Messenger でネットワーク対応回転灯を作る3

引き続き、PythonでIP Messengerのサービスにエントリして送られてくるメッセージを処理する辺りを。前回今回Raspberry Pi関係ないですね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import socket
import random
 
USER_NAME = 'ipmsg_light'
MACHINE_NAME = 'raspberrypi'
BROADCAST = '255.255.255.255'
 
IPMSG_VERSION           = 1
IPMSG_DEFAULT_PORT      = 2425
 
IPMSG_BR_ENTRY          = 0x00000001L
IPMSG_BR_EXIT           = 0x00000002L
IPMSG_ANSENTRY          = 0x00000003L
IPMSG_BR_ABSENCE        = 0x00000004L
IPMSG_SENDMSG           = 0x00000020L
IPMSG_RECVMSG           = 0x00000021L
 
PORT = IPMSG_DEFAULT_PORT
# -----
def turn_the_light_on():
    print 'light on.'
 
def turn_the_light_off():
    print 'light off.'
 
def get_mode(command):
    return command & 0x000000FFL
 
def get_msg_header(cmd, msg):
    packet_num = random.randrange(1, 10000000)
    return "%s:%s:%s:%s:%s:%s" % (IPMSG_VERSION, packet_num, USER_NAME, MACHINE_NAME, cmd, msg)
 
def get_ans_cmd(cmd):
    if cmd == IPMSG_BR_ENTRY:
        return IPMSG_ANSENTRY
    elif cmd == IPMSG_SENDMSG:
        return IPMSG_RECVMSG
 
    return -1
 
def set_light_status_by_user_status(user_dic):
    if len(user_dic) == 0:
        turn_the_light_off()
    elif reduce(lambda x,y: x+y, user_dic.values()) == 0:
        turn_the_light_off()
    else:
        turn_the_light_on()
 
def set_light_status(user, msg_text, user_dic):
    if user not in user_dic:
        user_dic[user] = 0
 
    msg_cmd = msg_text.replace("\0", '').strip().lower()
    if msg_cmd == "on":
        user_dic[user] = 1
    elif msg_cmd == 'off':
        user_dic[user] = 0
    elif msg_cmd == 'kill':
        user_dic.clear()
    else:
        user_dic[user] = (0 if user_dic[user] == 1 else 1)
 
    set_light_status_by_user_status(user_dic)
 
def remove_user(user, user_dic):
    if user in user_dic:
        user_dic.pop(user)
 
    set_light_status_by_user_status(user_dic)
 
def parse_recv_msg(msg):
    msg_ary = msg.split(':')
    if len(msg_ary) < 5 :
        return
 
    return (msg_ary[1], msg_ary[2], msg_ary[3], msg_ary[4], msg_ary[5])
 
# -----
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(('0.0.0.0', PORT))
 
msg = get_msg_header(IPMSG_BR_ENTRY, '')
s.sendto(msg, (BROADCAST, PORT))
 
user_dic = {}
 
while True:
    (recv_msg, client_addr) = s.recvfrom(1024)
 
    print recv_msg
 
    msg_tuple = parse_recv_msg(recv_msg)
    if msg_tuple is None:
        continue
 
    (packet_num, send_user, send_machine, recv_cmd, recv_msg_text) = msg_tuple
 
    cmd = get_mode(int(recv_cmd))
 
    if cmd == IPMSG_SENDMSG:
        set_light_status(send_user, recv_msg_text, user_dic)
    elif cmd == IPMSG_BR_EXIT:
        remove_user(send_user, user_dic)   
 
    ans_cmd = get_ans_cmd(cmd)
 
    if ans_cmd < 0 :
        continue
 
    ans_msg = ''
    if ans_cmd == IPMSG_ANSENTRY:
        ans_msg = get_msg_header(ans_cmd, USER_NAME)
    else :
        ans_msg = get_msg_header(ans_cmd, packet_num)
 
    s.sendto(ans_msg, client_addr)
 
s.close()

こんな感じで他のIP Messengerから送られてくるメッセージを処理できてる気がします。正直Python詳しくないので適当です。

基本的にメッセージ本文無しで回転灯をON/OFFさせるように考えていますが、本文にonかoffが書かれていればそれに従い、killとあれば他のユーザの状況にかかわらず常にOFFにするようにしてみました。

とりあえずメッセージを受けて回転灯の状態を操作できそうになったので、on/off文字をprintしている箇所を変更してRaspberry PiのGPIOを弄っていきます。

Raspberry Pi と IP Messenger でネットワーク対応回転灯を作る2

さて、Raspberry Piを使うとなるとやはり開発言語はPythonでしょう。

IP Messengerのプロトコルについてはこの辺りを参考にさせていただきます。 http://ipmsg.org/protocol.txt
http://libipmsg.sourceforge.jp/specification.html

今回はメッセージの送信は重要視しておらず、IP Messengerのユーザリストにリストアップされて、送られてくるメッセージを受け取ることができればOKです。 ざっくりまとめると、

  1. ネットワーク上のIP MessengerサービスにエントリするためのメッセージをUDP 2425でブロードキャスト。
  2. その後2425をリッスン。
  3. メッセージが来たら受け取ったよメッセージを返す。エントリメッセージの場合はエントリ受け付けたよメッセージを返す。

これだけです。 プロトコルフォーマットは
<バージョン>:<パケット番号>:<ユーザ名>:<ホスト名>:<コマンド番号>:<オプション部>
です。

バージョンは1固定でOKっぽいです。
パケット番号は乱数で。
ユーザ名とホスト名は適当に文字列を。
コマンド番号はIP Messengerのソースコードをダウンロードしてipmsg.hをみると早いです。エントリ時に送信するメッセージはこんな感じで定義されていました。

1
#define IPMSG_BR_ENTRY 0x00000001UL

とりあえずユーザリストにリストアップしてもらうところまでをさらっと。

1
2
3
4
5
6
7
8
9
10
11
12
import socket
import random
 
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(('0.0.0.0', 2425))
 
pnum = random.randrange(1, 100000)
msg = "%s:%s:%s:%s:%s:%s" % (1, pnum, 'ipmsg_light', 'raspberrypi', 1, '')
s.sendto(msg, ('255.255.255.255', 2425))
 
s.close()

これで実行すると、
実行前
これが
実行後_リストに表示された
こう。
ちゃんとエントリされました。