「RN-42Bluetoothモジュールの使い方2」の続きです。
最後にタブレットからPCのマウス、キーボードを遠隔操作してみたいと思います。
タブレットとRN42モジュールとはSPP通信を行い、タブレットからユーザ操作(スライドやタップなど)を送信して、PC側で信号の解読&マウス、キーボード操作を行います。
タブレット側には
・マウスモード
・キーボードモード
・ゲームパッドモード
を作って、それぞれ操作する対象を切り替えられるようにしたいと思います。
システム構成
ざっくりしたシステム構成です。
PC側のプログラムはPythonで記述しています。使用するライブラリは下記の通りです。
- Pyserial2.7 : シリアル通信をするために使用しています。
- ctypes1.0.2 : PythonからCで書かれたライブラリを呼び出すことができます。マウスやキーボード操作のために、WindowsのDLLを呼び出すのに使用しています。
- Guippy0.1.1 : WinwosGUIを簡単に操作するためのライブラリ。ctypesのラッパー。
通信仕様
Bluetooth SPP通信でやり取りするメッセージ仕様です。
とりあえず使いそうな操作を適当に決めてしまいます^^;
Mode | 操作 | message | 補足 |
---|---|---|---|
マウス | マウス移動 | ‘M’ (x) (y) | x=X軸の移動量(2byte), y=Y軸の移動量(2byte) |
右クリック | ‘R’ | ||
左クリック | ‘L’ | ||
ホイール | ‘O’ (d) | d=まわした量(手前<0, 奥>0) | |
右ドラッグ | ‘I’ | ドラッグ状態にする | |
左ドラッグ | ‘U’ | ドラッグ状態にする | |
ドロップ | ‘J’ | 左右のドラッグ状態を解除 | |
ゲームパッド | パッド操作 | ‘G’ (a) (b) | ボタン操作 1=On, 0=Off a(1byte): **000000 = 未使用 00*00000 = Start 000*0000 = Esc 0000*000 = 上 00000*00 = 下 000000*0 = 左 0000000* = 右 b(1byte): *0000000 = コンボ1 0*000000 = パンチ1 00*00000 = パンチ2 000*0000 = パンチ3 0000*000 = コンボ2 00000*00 = キック1 000000*0 = キック2 0000000* = キック3 |
キーボード | *未実装* | *未実装* | 時間の関係上、キーボードモードは未実装。 |
ゲームパッドモードは完全にストリートファイターV専用仕様にしてます。
キーボードモードは今回は未実装です・・・手抜きかな?
クライアントプログラムの実装
Bluetooth通信部分は、「RN-42Bluetoothモジュールの使い方2」と同じです。
後は、UI部分を実装するだけ。超簡単(適当)なので割愛。gitHub見てね。
今回送信するメッセージは最大でも5byteなので、最大送信時間は40/115200=0.00034s。
クライアント側で4mm以下で連続送信しないようにしてあげれば問題なさそうです。
サーバプログラムの実装
こちらもpyserialとguippy使うと簡単に作れました。
1 | # -*- coding: utf-8 -*- |
2 |
3 | import serial |
4 | import struct |
5 | import guippy |
6 | import time |
7 |
8 | ser = serial.Serial( 3 , 115200 ) |
9 | print ser.name |
10 | gp = guippy.Guippy() |
11 |
12 | isDragLeft = False |
13 | isDragRight = False |
14 |
15 | old_key = 0x0000 |
16 |
17 | while True : |
18 | cmd = ser.read() |
19 | print cmd |
20 | |
21 | old_time = time.clock() |
22 | if cmd = = 'M' : |
23 | x,y = struct.unpack( 'hh' , ser.read( 4 )) |
24 | gp.jump(x,y, True , False ) |
25 | print x,y |
26 | elif cmd = = 'R' : |
27 | gp.click(guippy.mouse.RIGHT) |
28 | elif cmd = = 'L' : |
29 | gp.click(guippy.mouse.LEFT) |
30 | elif cmd = = 'O' : |
31 | n, = struct.unpack( 'b' , ser.read()) |
32 | gp.wheel(n) |
33 | print n |
34 | elif cmd = = 'U' : |
35 | isDragLeft = True |
36 | gp.drag(guippy.mouse.LEFT) |
37 | elif cmd = = 'I' : |
38 | isDragRight = True |
39 | gp.drag(guippy.mouse.RIGHT) |
40 | elif cmd = = 'J' : |
41 | if isDragLeft: gp.drop(guippy.mouse.LEFT) |
42 | if isDragRight: gp.drop(guippy.mouse.RIGHT) |
43 | elif cmd = = 'G' : |
44 | key, = struct.unpack( 'h' , ser.read( 2 )) |
45 | print key |
46 | |
47 | if old_key - key <> 0 : |
48 | if key& 0x2000 > 0 : #START |
49 | gp.enter() |
50 | if key& 0x1000 > 0 : #ESC |
51 | gp.escape() |
52 | if key& 0x0800 > 0 : #UP |
53 | gp.push( 38 ) |
54 | else : |
55 | gp.release( 38 ) |
56 | if key& 0x0400 > 0 : #DOWN |
57 | gp.push( 40 ) |
58 | else : |
59 | gp.release( 40 ) |
60 | if key& 0x0200 > 0 : #LEFT |
61 | gp.push( 37 ) |
62 | else : |
63 | gp.release( 37 ) |
64 | if key& 0x0100 > 0 : #RIGHT |
65 | gp.push( 39 ) |
66 | else : |
67 | gp.release( 39 ) |
68 | if key& 0x0080 > 0 : #Combo1 |
69 | gp.punch( 'o' ) |
70 | gp.push( 39 ) |
71 | gp.release( 39 ) |
72 | gp.push( 40 ) |
73 | gp.release( 40 ) |
74 | gp.push( 39 ) |
75 | gp.release( 39 ) |
76 | gp.punch( 'u' ) |
77 |
78 | gp.push( 40 ) |
79 | gp.push( 39 ) |
80 | gp.release( 40 ) |
81 | gp.release( 39 ) |
82 | gp.push( 40 ) |
83 | gp.push( 39 ) |
84 | gp.release( 40 ) |
85 | gp.release( 39 ) |
86 | gp.punch( 'o' ) |
87 |
88 | if key& 0x0040 > 0 : #P1 |
89 | gp.push( 85 ) |
90 | else : |
91 | gp.release( 85 ) |
92 | if key& 0x0020 > 0 : #P2 |
93 | gp.push( 73 ) |
94 | else : |
95 | gp.release( 73 ) |
96 | if key& 0x0010 > 0 : #P3 |
97 | gp.push( 79 ) |
98 | else : |
99 | gp.release( 79 ) |
100 | if key& 0x0008 > 0 : #Combo2 |
101 | gp.push( 40 ) |
102 | gp.push( 39 ) |
103 | gp.release( 40 ) |
104 | gp.push( 79 ) |
105 | gp.release( 39 ) |
106 |
107 | if key& 0x0004 > 0 : #K1 |
108 | gp.push( 74 ) |
109 | else : |
110 | gp.release( 74 ) |
111 | if key& 0x0002 > 0 : #K2 |
112 | gp.push( 75 ) |
113 | else : |
114 | gp.release( 75 ) |
115 | if key& 0x0001 > 0 : #K3 |
116 | gp.push( 76 ) |
117 | else : |
118 | gp.release( 76 ) |
119 | old_key = key |
120 |
121 | elif cmd = = 'E' : |
122 | break |
123 |
124 | new_time = time.clock() |
125 | elapsed = new_time - old_time |
126 | print elapsed |
127 | old_time = new_time |
128 |
129 | ser.close() |
一番苦労したのは68-86行目、100-105行目のコンボのところですね(笑
あと、ゲームパッドモードの操作は結局キーボード操作(ゲーム側でUは弱パンチとかに設定している)にしているんですが・・・
Guippyのキーボード操作(push,releaseなど)が処理完了までに200msぐらいかかかるようで、格闘ゲームにはまったく使えなかったという。
#かろうじて波動拳、昇竜拳コマンドはいけました
動かしてみたよ
さっそく動かしてみました。
やったー!動いた!
・・・まぁ、あまり実用的ではないですが。
あとがき
3回にわたってRN42 Bluetoothモジュールの使い方を書いてみました。
基本的な使い方は理解できたかなと思います。
サンプルで作ったプログラムは、あまり実用的ではないですが、スマホをリモコン代わりに使えるようにしたら、もっと実用的にできるかなとも思いました。
例えば、ボタンひとつで、
・メーラーを立ち上げ自動読み上げ
・YouTubeを開いてプレイリストを自動再生
・PCのシャットダウン
とかは、簡単に実装できそうです。
今回のサンプルプログラムは下記においておきますね。
https://github.com/workpiles/BtSample
では。