blob: 3a138c06d7149a0d611fcd8b863faa3db1c75372 [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):
Joseph Hwang9f0e0242016-06-22 12:50:20 +080021 """A base bluetooth HID emulator class using RN-42 evaluation kit.
22
23 Note: every public member method should
24 return True or a non-None object if successful;
25 return False or Raise an exception otherwise.
26 """
Joseph Hwang5d0dc642016-01-19 10:21:12 +080027
Joseph Hwangd13e5e72016-05-30 18:57:35 +080028 TMP_PIN_CODE = '0000' # A temporary pin code
Joseph Hwang5d0dc642016-01-19 10:21:12 +080029
30 SEND_DELAY_SECS = 0.2 # Need to sleep for a short while otherwise
31 # the bits may get lost during transmission.
Joseph Hwangd13e5e72016-05-30 18:57:35 +080032 INIT_SLEEP_SECS = 5 # Sleep after initialization for stabilization.
Joseph Hwang5d0dc642016-01-19 10:21:12 +080033
34 def __init__(self, device_type, authentication_mode,
35 send_delay=SEND_DELAY_SECS):
36 """Initialization of BluetoothHID
37
38 Args:
39 device_type: the device type for emulation
40 authentication_mode: the authentication mode
41 send_delay: wait a while after sending data
42 """
43 super(BluetoothHID, self).__init__()
44 self.device_type = device_type
Joseph Hwangc1edeff2016-06-08 15:38:14 +080045 self.authentication_mode = authentication_mode
Joseph Hwang5d0dc642016-01-19 10:21:12 +080046 self.send_delay = send_delay
47
Joseph Hwangd13e5e72016-05-30 18:57:35 +080048 def Init(self, factory_reset=True):
49 """Initialize the chip correctly.
Joseph Hwang5d0dc642016-01-19 10:21:12 +080050
Joseph Hwangd13e5e72016-05-30 18:57:35 +080051 Initialize the chip with proper HID register values.
Joseph Hwang5d0dc642016-01-19 10:21:12 +080052
Joseph Hwangd13e5e72016-05-30 18:57:35 +080053 Args:
54 factory_reset: True if a factory reset is needed.
55 False if we only want to reconnect the serial device.
56 """
57 # Create a new serial device every time since the serial driver
58 # on chameleon board is not very stable.
59 self.CreateSerialDevice()
Joseph Hwang5d0dc642016-01-19 10:21:12 +080060
Joseph Hwangd13e5e72016-05-30 18:57:35 +080061 if factory_reset:
62 # Do a factory reset to make sure it is in a known initial state.
63 # Do the factory reset before proceeding to set parameters below.
64 self.FactoryReset()
Joseph Hwang5d0dc642016-01-19 10:21:12 +080065
Joseph Hwangd13e5e72016-05-30 18:57:35 +080066 # Enter command mode to issue commands.
67 self.EnterCommandMode()
Joseph Hwang5d0dc642016-01-19 10:21:12 +080068
Joseph Hwangd13e5e72016-05-30 18:57:35 +080069 # Set HID as the service profile.
70 self.SetServiceProfileHID()
Joseph Hwang5d0dc642016-01-19 10:21:12 +080071
Joseph Hwangd13e5e72016-05-30 18:57:35 +080072 # Set the HID device type.
73 self.SetHIDDevice(self.device_type)
Joseph Hwang5d0dc642016-01-19 10:21:12 +080074
Joseph Hwangd13e5e72016-05-30 18:57:35 +080075 # Set the default class of service.
76 self.SetDefaultClassOfService()
Joseph Hwang5d0dc642016-01-19 10:21:12 +080077
Joseph Hwangd13e5e72016-05-30 18:57:35 +080078 # Set the class of device (CoD) according to the hid device type.
79 self.SetClassOfDevice(self.device_type)
80
81 # Set authentication to the specified mode.
82 self.SetAuthenticationMode(self.authentication_mode)
83
84 # Set RN-42 to work as a slave.
85 self.SetSlaveMode()
86
87 # Enable the connection status message so that we could get the message
88 # of connection/disconnection status.
89 self.EnableConnectionStatusMessage()
90
91 # Set a temporary pin code for testing purpose.
92 self.SetPinCode(self.TMP_PIN_CODE)
93
94 # Reboot so that the configurations above take effect.
95 self.Reboot()
96
97 # Enter command mode again after reboot.
98 self.EnterCommandMode()
99
100 time.sleep(self.INIT_SLEEP_SECS)
101
102 logging.info('A bluetooth HID "%s" device is connected.', self.device_type)
Joseph Hwang9f0e0242016-06-22 12:50:20 +0800103 return True
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800104
105 def __del__(self):
106 self.Close()
107
108 def SetHIDDevice(self, device_type):
109 """Set HID device to the specified device type.
110
111 Args:
112 device_type: the HID device type to emulate
113 """
114 if device_type == self.KEYBOARD:
115 self.SetHIDKeyboard()
116 elif device_type == self.GAMEPAD:
117 self.SetHIDGamepad()
118 elif device_type == self.MOUSE:
119 self.SetHIDMouse()
120 elif device_type == self.COMBO:
121 self.SetHIDCombo()
122 elif device_type == self.JOYSTICK:
123 self.SetHIDJoystick()
124
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800125
126class BluetoothHIDKeyboard(BluetoothHID):
127 """A bluetooth HID keyboard emulator class."""
128
129 def __init__(self, authentication_mode):
130 """Initialization of BluetoothHIDKeyboard
131
132 Args:
133 authentication_mode: the authentication mode
134 """
135 super(BluetoothHIDKeyboard, self).__init__(BluetoothHID.KEYBOARD,
136 authentication_mode)
137
138 def Send(self, data):
139 """Send data to the remote host.
140
141 Args:
142 data: data to send to the remote host
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800143 data could be either a string of printable ASCII characters or
144 a special key combination.
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800145 """
146 # TODO(josephsih): should have a method to check the connection status.
147 # Currently, once RN-42 is connected to a remote host, all characters
148 # except chr(0) transmitted through the serial port are interpreted
149 # as characters to send to the remote host.
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800150 logging.debug('HID device sending %r...', data)
151 self.SerialSendReceive(data, msg='BluetoothHID.Send')
152 time.sleep(self.send_delay)
153
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800154 def SendKeyCombination(self, modifiers=None, keys=None):
155 """Send special key combinations to the remote host.
156
157 Args:
158 modifiers: a list of modifiers
159 keys: a list of scan codes of keys
160 """
161 press_codes = self.PressShorthandCodes(modifiers=modifiers, keys=keys)
162 release_codes = self.ReleaseShorthandCodes()
163 if press_codes and release_codes:
164 self.Send(press_codes)
165 self.Send(release_codes)
166 else:
167 logging.warn('modifers: %s and keys: %s are not valid', modifiers, keys)
168 return None
169
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800170
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800171class BluetoothHIDMouse(BluetoothHID):
172 """A bluetooth HID mouse emulator class."""
173
174 # Definitions of buttons
175 BUTTONS_RELEASED = 0x0
176 LEFT_BUTTON = 0x01
177 RIGHT_BUTTON = 0x02
178
179 def __init__(self, authentication_mode):
180 """Initialization of BluetoothHIDMouse
181
182 Args:
183 authentication_mode: the authentication mode
184 """
185 super(BluetoothHIDMouse, self).__init__(BluetoothHID.MOUSE,
186 authentication_mode)
187
188 def Move(self, delta_x=0, delta_y=0):
189 """Move the mouse (delta_x, delta_y) pixels.
190
191 Args:
192 delta_x: the pixels to move horizontally
193 positive values: moving right; max value = 127.
194 negative values: moving left; max value = -127.
195 delta_y: the pixels to move vertically
196 positive values: moving down; max value = 127.
197 negative values: moving up; max value = -127.
198 """
199 if delta_x or delta_y:
200 mouse_codes = self.RawMouseCodes(x_stop=delta_x, y_stop=delta_y)
201 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse.Move')
202 time.sleep(self.send_delay)
203
204 def _PressButtons(self, buttons):
205 """Press down the specified buttons
206
207 Args:
208 buttons: the buttons to press
209 """
210 if buttons:
211 mouse_codes = self.RawMouseCodes(buttons=buttons)
212 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse._PressButtons')
213 time.sleep(self.send_delay)
214
215 def _ReleaseButtons(self):
216 """Release buttons."""
217 mouse_codes = self.RawMouseCodes(buttons=self.BUTTONS_RELEASED)
218 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse._ReleaseButtons')
219 time.sleep(self.send_delay)
220
221 def PressLeftButton(self):
222 """Press the left button."""
223 self._PressButtons(self.LEFT_BUTTON)
224
225 def ReleaseLeftButton(self):
226 """Release the left button."""
227 self._ReleaseButtons()
228
229 def PressRightButton(self):
230 """Press the right button."""
231 self._PressButtons(self.RIGHT_BUTTON)
232
233 def ReleaseRightButton(self):
234 """Release the right button."""
235 self._ReleaseButtons()
236
237 def LeftClick(self):
238 """Make a left click."""
239 self.PressLeftButton()
240 self.ReleaseLeftButton()
241
242 def RightClick(self):
243 """Make a right click."""
244 self.PressRightButton()
245 self.ReleaseRightButton()
246
247 def ClickAndDrag(self, delta_x=0, delta_y=0):
248 """Click and drag (delta_x, delta_y)
249
250 Args:
251 delta_x: the pixels to move horizontally
252 delta_y: the pixels to move vertically
253 """
254 self.PressLeftButton()
255 self.Move(delta_x, delta_y)
256 self.ReleaseLeftButton()
257
258 def Scroll(self, wheel):
259 """Scroll the wheel.
260
261 Args:
262 wheel: the steps to scroll
263 The scroll direction depends on which scroll method is employed,
264 traditional scrolling or Australian scrolling.
265 """
266 if wheel:
267 mouse_codes = self.RawMouseCodes(wheel=wheel)
268 self.SerialSendReceive(mouse_codes, msg='BluetoothHIDMouse.Scroll')
269 time.sleep(self.send_delay)
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800270
271
272def DemoBluetoothHIDKeyboard(remote_address, chars):
273 """A simple demo of acting as a HID keyboard.
274
275 This simple demo works only after the HID device has already paired
276 with the remote device such that a link key has been exchanged. Then
277 the HID device could connect directly to the remote host without
278 pin code and sends the message.
279
280 A full flow would be letting a remote host pair with the HID device
281 with the pin code of the HID device. Thereafter, either the host or
282 the HID device could request to connect. This is out of the scope of
283 this simple demo.
284
285 Args:
286 remote_address: the bluetooth address of the target remote device
287 chars: the characters to send
288 """
289 print 'Creating an emulated bluetooth keyboard...'
290 keyboard = BluetoothHIDKeyboard(BluetoothHID.PIN_CODE_MODE)
Joseph Hwangc1edeff2016-06-08 15:38:14 +0800291 keyboard.Init()
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800292
293 print 'Connecting to the remote address %s...' % remote_address
294 try:
295 if keyboard.ConnectToRemoteAddress(remote_address):
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800296 # Send printable ASCII strings a few times.
297 for i in range(1, 4):
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800298 print 'Sending "%s" for the %dth time...' % (chars, i)
299 keyboard.Send(chars + ' ' + str(i))
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800300
301 # Demo special key combinations below.
302 print 'Create a new chrome tab.'
303 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL],
304 keys=[RN42.SCAN_T])
305
306 print 'Navigate to Google page.'
307 keyboard.Send('www.google.com')
308 time.sleep(1)
309
310 print 'Search hello world.'
311 keyboard.Send('hello world')
312 time.sleep(1)
313
314 print 'Navigate back to the previous page.'
315 keyboard.SendKeyCombination(keys=[RN42.SCAN_F1])
316 time.sleep(1)
317
318 print 'Switch to the previous tab.'
319 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL, RN42.LEFT_SHIFT],
320 keys=[RN42.SCAN_TAB])
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800321 else:
322 print 'Something is wrong. Not able to connect to the remote address.'
323 print 'Have you already paired RN-42 with the remote host?'
324 finally:
325 print 'Disconnecting...'
326 keyboard.Disconnect()
327
328 print 'Closing the keyboard...'
329 keyboard.Close()
330
331
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800332def DemoBluetoothHIDMouse(remote_address):
333 """A simple demo of acting as a HID mouse.
334
335 Args:
336 remote_address: the bluetooth address of the target remote device
337 """
338 print 'Creating an emulated bluetooth mouse...'
339 mouse = BluetoothHIDMouse(BluetoothHID.PIN_CODE_MODE)
Joseph Hwangc1edeff2016-06-08 15:38:14 +0800340 mouse.Init()
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800341
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800342 connected = False
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800343 print 'Connecting to the remote address %s...' % remote_address
344 try:
345 if mouse.ConnectToRemoteAddress(remote_address):
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800346 connected = True
347
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800348 print 'Click and drag horizontally.'
349 mouse.ClickAndDrag(delta_x=100)
350 time.sleep(1)
351
352 print 'Make a right click.'
353 mouse.RightClick()
354 time.sleep(1)
355
356 print 'Move the cursor upper left.'
357 mouse.Move(delta_x=-30, delta_y=-40)
358 time.sleep(1)
359
360 print 'Make a left click.'
361 mouse.LeftClick()
362 time.sleep(1)
363
364 print 'Move the cursor left.'
365 mouse.Move(delta_x=-100)
366 time.sleep(1)
367
368 print 'Move the cursor up.'
369 mouse.Move(delta_y=-90)
370 time.sleep(1)
371
372 print 'Move the cursor down right.'
373 mouse.Move(delta_x=100, delta_y=90)
374 time.sleep(1)
375
376 print 'Scroll in one direction.'
377 mouse.Scroll(-80)
378 time.sleep(1)
379
380 print 'Scroll in the opposite direction.'
381 mouse.Scroll(100)
382 else:
383 print 'Something is wrong. Not able to connect to the remote address.'
384 finally:
Joseph Hwang24ed76a2016-05-27 13:20:48 +0800385 if connected:
386 print 'Disconnecting...'
387 try:
388 mouse.Disconnect()
389 except RN42Exception:
390 # RN-42 may have already disconnected.
391 pass
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800392
393 print 'Closing the mouse...'
394 mouse.Close()
395
396
397def _Parse():
398 """Parse the command line options."""
399 prog = sys.argv[0]
400 example_usage = ('Example:\n' +
401 ' python %s keyboard 00:11:22:33:44:55\n' % prog +
402 ' python %s mouse 00:11:22:33:44:55\n'% prog)
403 parser = argparse.ArgumentParser(
404 description='Emulate a HID device.\n' + example_usage,
405 formatter_class=argparse.RawTextHelpFormatter)
406 parser.add_argument('device',
407 choices=['keyboard', 'mouse'],
408 help='the device type to emulate')
409 parser.add_argument('remote_host_address',
410 help='the remote host address')
411 parser.add_argument('-c', '--chars_to_send',
412 default='echo hello world',
413 help='characters to send to the remote host')
414 args = parser.parse_args()
415
416 if len(args.remote_host_address.replace(':', '')) != 12:
417 print '"%s" is not a valid bluetooth address.' % args.remote_host_address
418 exit(1)
419
420 print ('Emulate a %s and connect to remote host at %s' %
421 (args.device, args.remote_host_address))
422 return args
423
424
425def Demo():
426 """Make demonstrations about how to use the HID emulation classes."""
427 args = _Parse()
428 device = args.device.lower()
429 if device == 'keyboard':
430 DemoBluetoothHIDKeyboard(args.remote_host_address, args.chars_to_send)
431 elif device == 'mouse':
432 DemoBluetoothHIDMouse(args.remote_host_address)
433 else:
434 args.print_help()
435
436
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800437if __name__ == '__main__':
Joseph Hwangd3784cb2016-03-06 21:57:39 +0800438 Demo()