blob: daefc150eda75c4dd7356376177afdc48c08a56f [file] [log] [blame]
Joseph Hwang5d0dc642016-01-19 10:21:12 +08001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This module provides emulation of bluetooth HID devices."""
6
Joseph Hwangd3784cb2016-03-06 21:57:39 +08007import argparse
Joseph Hwang5d0dc642016-01-19 10:21:12 +08008import logging
9import sys
10import time
11
Joseph Hwangd3784cb2016-03-06 21:57:39 +080012from bluetooth_rn42 import RN42, RN42Exception
Joseph Hwang5d0dc642016-01-19 10:21:12 +080013
14
15class BluetoothHIDException(Exception):
16 """A dummpy exception class for Bluetooth HID class."""
17 pass
18
19
20class BluetoothHID(RN42):
21 """A base bluetooth HID emulator class using RN-42 evaluation kit."""
22
23 # Suppoerted device types
24 KEYBOARD = 'KEYBOARD'
25 GAMEPAD = 'GAMEPAD'
26 MOUSE = 'MOUSE'
27 COMBO = 'COMBO'
28 JOYSTICK = 'JOYSTICK'
29
30 SEND_DELAY_SECS = 0.2 # Need to sleep for a short while otherwise
31 # the bits may get lost during transmission.
32
33 def __init__(self, device_type, authentication_mode,
34 send_delay=SEND_DELAY_SECS):
35 """Initialization of BluetoothHID
36
37 Args:
38 device_type: the device type for emulation
39 authentication_mode: the authentication mode
40 send_delay: wait a while after sending data
41 """
42 super(BluetoothHID, self).__init__()
43 self.device_type = device_type
Joseph Hwangc1edeff2016-06-08 15:38:14 +080044 self.authentication_mode = authentication_mode
Joseph Hwang5d0dc642016-01-19 10:21:12 +080045 self.send_delay = send_delay
46
Joseph Hwangc1edeff2016-06-08 15:38:14 +080047 def Init(self):
48 """Initialize the emulated device."""
Joseph Hwang5d0dc642016-01-19 10:21:12 +080049 # Enter command mode for configuration.
50 self.EnterCommandMode()
51
52 # Set HID as the service profile.
53 self.SetServiceProfileHID()
54
55 # Set the HID device type.
Joseph Hwangc1edeff2016-06-08 15:38:14 +080056 self.SetHIDDevice(self.device_type)
Joseph Hwang5d0dc642016-01-19 10:21:12 +080057
58 # Set authentication to the specified mode.
Joseph Hwangc1edeff2016-06-08 15:38:14 +080059 self.SetAuthenticationMode(self.authentication_mode)
Joseph Hwang5d0dc642016-01-19 10:21:12 +080060
61 # Set RN-42 to work as a slave.
62 self.SetSlaveMode()
63
64 # Enable the connection status message so that we could get the message
65 # of connection/disconnection status.
66 self.EnableConnectionStatusMessage()
67
68 # Reboot so that the configurations above take in effect.
69 self.Reboot()
70
71 # Should enter command mode again after reboot.
72 self.EnterCommandMode()
73
Joseph Hwangc1edeff2016-06-08 15:38:14 +080074 logging.info('A HID "%s" device is created successfully.', self.device_type)
Joseph Hwang5d0dc642016-01-19 10:21:12 +080075
76 def __del__(self):
77 self.Close()
78
79 def SetHIDDevice(self, device_type):
80 """Set HID device to the specified device type.
81
82 Args:
83 device_type: the HID device type to emulate
84 """
85 if device_type == self.KEYBOARD:
86 self.SetHIDKeyboard()
87 elif device_type == self.GAMEPAD:
88 self.SetHIDGamepad()
89 elif device_type == self.MOUSE:
90 self.SetHIDMouse()
91 elif device_type == self.COMBO:
92 self.SetHIDCombo()
93 elif device_type == self.JOYSTICK:
94 self.SetHIDJoystick()
95
Joseph Hwang5d0dc642016-01-19 10:21:12 +080096
97class BluetoothHIDKeyboard(BluetoothHID):
98 """A bluetooth HID keyboard emulator class."""
99
100 def __init__(self, authentication_mode):
101 """Initialization of BluetoothHIDKeyboard
102
103 Args:
104 authentication_mode: the authentication mode
105 """
106 super(BluetoothHIDKeyboard, self).__init__(BluetoothHID.KEYBOARD,
107 authentication_mode)
108
109 def Send(self, data):
110 """Send data to the remote host.
111
112 Args:
113 data: data to send to the remote host
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800114 data could be either a string of printable ASCII characters or
115 a special key combination.
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800116 """
117 # TODO(josephsih): should have a method to check the connection status.
118 # Currently, once RN-42 is connected to a remote host, all characters
119 # except chr(0) transmitted through the serial port are interpreted
120 # as characters to send to the remote host.
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800121 logging.debug('HID device sending %r...', data)
122 self.SerialSendReceive(data, msg='BluetoothHID.Send')
123 time.sleep(self.send_delay)
124
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800125 def SendKeyCombination(self, modifiers=None, keys=None):
126 """Send special key combinations to the remote host.
127
128 Args:
129 modifiers: a list of modifiers
130 keys: a list of scan codes of keys
131 """
132 press_codes = self.PressShorthandCodes(modifiers=modifiers, keys=keys)
133 release_codes = self.ReleaseShorthandCodes()
134 if press_codes and release_codes:
135 self.Send(press_codes)
136 self.Send(release_codes)
137 else:
138 logging.warn('modifers: %s and keys: %s are not valid', modifiers, keys)
139 return None
140
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800141
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800142class BluetoothHIDMouse(BluetoothHID):
143 """A bluetooth HID mouse emulator class."""
144
145 # Definitions of buttons
146 BUTTONS_RELEASED = 0x0
147 LEFT_BUTTON = 0x01
148 RIGHT_BUTTON = 0x02
149
150 def __init__(self, authentication_mode):
151 """Initialization of BluetoothHIDMouse
152
153 Args:
154 authentication_mode: the authentication mode
155 """
156 super(BluetoothHIDMouse, self).__init__(BluetoothHID.MOUSE,
157 authentication_mode)
158
159 def Move(self, delta_x=0, delta_y=0):
160 """Move the mouse (delta_x, delta_y) pixels.
161
162 Args:
163 delta_x: the pixels to move horizontally
164 positive values: moving right; max value = 127.
165 negative values: moving left; max value = -127.
166 delta_y: the pixels to move vertically
167 positive values: moving down; max value = 127.
168 negative values: moving up; max value = -127.
169 """
170 if delta_x or delta_y:
171 mouse_codes = self.RawMouseCodes(x_stop=delta_x, y_stop=delta_y)
172 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse.Move')
173 time.sleep(self.send_delay)
174
175 def _PressButtons(self, buttons):
176 """Press down the specified buttons
177
178 Args:
179 buttons: the buttons to press
180 """
181 if buttons:
182 mouse_codes = self.RawMouseCodes(buttons=buttons)
183 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse._PressButtons')
184 time.sleep(self.send_delay)
185
186 def _ReleaseButtons(self):
187 """Release buttons."""
188 mouse_codes = self.RawMouseCodes(buttons=self.BUTTONS_RELEASED)
189 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse._ReleaseButtons')
190 time.sleep(self.send_delay)
191
192 def PressLeftButton(self):
193 """Press the left button."""
194 self._PressButtons(self.LEFT_BUTTON)
195
196 def ReleaseLeftButton(self):
197 """Release the left button."""
198 self._ReleaseButtons()
199
200 def PressRightButton(self):
201 """Press the right button."""
202 self._PressButtons(self.RIGHT_BUTTON)
203
204 def ReleaseRightButton(self):
205 """Release the right button."""
206 self._ReleaseButtons()
207
208 def LeftClick(self):
209 """Make a left click."""
210 self.PressLeftButton()
211 self.ReleaseLeftButton()
212
213 def RightClick(self):
214 """Make a right click."""
215 self.PressRightButton()
216 self.ReleaseRightButton()
217
218 def ClickAndDrag(self, delta_x=0, delta_y=0):
219 """Click and drag (delta_x, delta_y)
220
221 Args:
222 delta_x: the pixels to move horizontally
223 delta_y: the pixels to move vertically
224 """
225 self.PressLeftButton()
226 self.Move(delta_x, delta_y)
227 self.ReleaseLeftButton()
228
229 def Scroll(self, wheel):
230 """Scroll the wheel.
231
232 Args:
233 wheel: the steps to scroll
234 The scroll direction depends on which scroll method is employed,
235 traditional scrolling or Australian scrolling.
236 """
237 if wheel:
238 mouse_codes = self.RawMouseCodes(wheel=wheel)
239 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse.Scroll')
240 time.sleep(self.send_delay)
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800241
242
243def DemoBluetoothHIDKeyboard(remote_address, chars):
244 """A simple demo of acting as a HID keyboard.
245
246 This simple demo works only after the HID device has already paired
247 with the remote device such that a link key has been exchanged. Then
248 the HID device could connect directly to the remote host without
249 pin code and sends the message.
250
251 A full flow would be letting a remote host pair with the HID device
252 with the pin code of the HID device. Thereafter, either the host or
253 the HID device could request to connect. This is out of the scope of
254 this simple demo.
255
256 Args:
257 remote_address: the bluetooth address of the target remote device
258 chars: the characters to send
259 """
260 print 'Creating an emulated bluetooth keyboard...'
261 keyboard = BluetoothHIDKeyboard(BluetoothHID.PIN_CODE_MODE)
Joseph Hwangc1edeff2016-06-08 15:38:14 +0800262 keyboard.Init()
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800263
264 print 'Connecting to the remote address %s...' % remote_address
265 try:
266 if keyboard.ConnectToRemoteAddress(remote_address):
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800267 # Send printable ASCII strings a few times.
268 for i in range(1, 4):
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800269 print 'Sending "%s" for the %dth time...' % (chars, i)
270 keyboard.Send(chars + ' ' + str(i))
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800271
272 # Demo special key combinations below.
273 print 'Create a new chrome tab.'
274 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL],
275 keys=[RN42.SCAN_T])
276
277 print 'Navigate to Google page.'
278 keyboard.Send('www.google.com')
279 time.sleep(1)
280
281 print 'Search hello world.'
282 keyboard.Send('hello world')
283 time.sleep(1)
284
285 print 'Navigate back to the previous page.'
286 keyboard.SendKeyCombination(keys=[RN42.SCAN_F1])
287 time.sleep(1)
288
289 print 'Switch to the previous tab.'
290 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL, RN42.LEFT_SHIFT],
291 keys=[RN42.SCAN_TAB])
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800292 else:
293 print 'Something is wrong. Not able to connect to the remote address.'
294 print 'Have you already paired RN-42 with the remote host?'
295 finally:
296 print 'Disconnecting...'
297 keyboard.Disconnect()
298
299 print 'Closing the keyboard...'
300 keyboard.Close()
301
302
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800303def DemoBluetoothHIDMouse(remote_address):
304 """A simple demo of acting as a HID mouse.
305
306 Args:
307 remote_address: the bluetooth address of the target remote device
308 """
309 print 'Creating an emulated bluetooth mouse...'
310 mouse = BluetoothHIDMouse(BluetoothHID.PIN_CODE_MODE)
Joseph Hwangc1edeff2016-06-08 15:38:14 +0800311 mouse.Init()
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800312
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800313 connected = False
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800314 print 'Connecting to the remote address %s...' % remote_address
315 try:
316 if mouse.ConnectToRemoteAddress(remote_address):
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800317 connected = True
318
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800319 print 'Click and drag horizontally.'
320 mouse.ClickAndDrag(delta_x=100)
321 time.sleep(1)
322
323 print 'Make a right click.'
324 mouse.RightClick()
325 time.sleep(1)
326
327 print 'Move the cursor upper left.'
328 mouse.Move(delta_x=-30, delta_y=-40)
329 time.sleep(1)
330
331 print 'Make a left click.'
332 mouse.LeftClick()
333 time.sleep(1)
334
335 print 'Move the cursor left.'
336 mouse.Move(delta_x=-100)
337 time.sleep(1)
338
339 print 'Move the cursor up.'
340 mouse.Move(delta_y=-90)
341 time.sleep(1)
342
343 print 'Move the cursor down right.'
344 mouse.Move(delta_x=100, delta_y=90)
345 time.sleep(1)
346
347 print 'Scroll in one direction.'
348 mouse.Scroll(-80)
349 time.sleep(1)
350
351 print 'Scroll in the opposite direction.'
352 mouse.Scroll(100)
353 else:
354 print 'Something is wrong. Not able to connect to the remote address.'
355 finally:
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800356 if connected:
357 print 'Disconnecting...'
358 try:
359 mouse.Disconnect()
360 except RN42Exception:
361 # RN-42 may have already disconnected.
362 pass
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800363
364 print 'Closing the mouse...'
365 mouse.Close()
366
367
368def _Parse():
369 """Parse the command line options."""
370 prog = sys.argv[0]
371 example_usage = ('Example:\n' +
372 ' python %s keyboard 00:11:22:33:44:55\n' % prog +
373 ' python %s mouse 00:11:22:33:44:55\n'% prog)
374 parser = argparse.ArgumentParser(
375 description='Emulate a HID device.\n' + example_usage,
376 formatter_class=argparse.RawTextHelpFormatter)
377 parser.add_argument('device',
378 choices=['keyboard', 'mouse'],
379 help='the device type to emulate')
380 parser.add_argument('remote_host_address',
381 help='the remote host address')
382 parser.add_argument('-c', '--chars_to_send',
383 default='echo hello world',
384 help='characters to send to the remote host')
385 args = parser.parse_args()
386
387 if len(args.remote_host_address.replace(':', '')) != 12:
388 print '"%s" is not a valid bluetooth address.' % args.remote_host_address
389 exit(1)
390
391 print ('Emulate a %s and connect to remote host at %s' %
392 (args.device, args.remote_host_address))
393 return args
394
395
396def Demo():
397 """Make demonstrations about how to use the HID emulation classes."""
398 args = _Parse()
399 device = args.device.lower()
400 if device == 'keyboard':
401 DemoBluetoothHIDKeyboard(args.remote_host_address, args.chars_to_send)
402 elif device == 'mouse':
403 DemoBluetoothHIDMouse(args.remote_host_address)
404 else:
405 args.print_help()
406
407
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800408if __name__ == '__main__':
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800409 Demo()