blob: 551867e0f6fc649e431d513b74ba59aa3ce91921 [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
7import logging
8import sys
9import time
10
11from bluetooth_rn42 import RN42
12
13
14class BluetoothHIDException(Exception):
15 """A dummpy exception class for Bluetooth HID class."""
16 pass
17
18
19class BluetoothHID(RN42):
20 """A base bluetooth HID emulator class using RN-42 evaluation kit."""
21
22 # Suppoerted device types
23 KEYBOARD = 'KEYBOARD'
24 GAMEPAD = 'GAMEPAD'
25 MOUSE = 'MOUSE'
26 COMBO = 'COMBO'
27 JOYSTICK = 'JOYSTICK'
28
29 SEND_DELAY_SECS = 0.2 # Need to sleep for a short while otherwise
30 # the bits may get lost during transmission.
31
32 def __init__(self, device_type, authentication_mode,
33 send_delay=SEND_DELAY_SECS):
34 """Initialization of BluetoothHID
35
36 Args:
37 device_type: the device type for emulation
38 authentication_mode: the authentication mode
39 send_delay: wait a while after sending data
40 """
41 super(BluetoothHID, self).__init__()
42 self.device_type = device_type
43 self.send_delay = send_delay
44
45 # Enter command mode for configuration.
46 self.EnterCommandMode()
47
48 # Set HID as the service profile.
49 self.SetServiceProfileHID()
50
51 # Set the HID device type.
52 self.SetHIDDevice(device_type)
53
54 # Set authentication to the specified mode.
55 self.SetAuthenticationMode(authentication_mode)
56
57 # Set RN-42 to work as a slave.
58 self.SetSlaveMode()
59
60 # Enable the connection status message so that we could get the message
61 # of connection/disconnection status.
62 self.EnableConnectionStatusMessage()
63
64 # Reboot so that the configurations above take in effect.
65 self.Reboot()
66
67 # Should enter command mode again after reboot.
68 self.EnterCommandMode()
69
70 logging.info('A HID "%s" device is created successfully.', device_type)
71
72 def __del__(self):
73 self.Close()
74
75 def SetHIDDevice(self, device_type):
76 """Set HID device to the specified device type.
77
78 Args:
79 device_type: the HID device type to emulate
80 """
81 if device_type == self.KEYBOARD:
82 self.SetHIDKeyboard()
83 elif device_type == self.GAMEPAD:
84 self.SetHIDGamepad()
85 elif device_type == self.MOUSE:
86 self.SetHIDMouse()
87 elif device_type == self.COMBO:
88 self.SetHIDCombo()
89 elif device_type == self.JOYSTICK:
90 self.SetHIDJoystick()
91
92 def Send(self, data):
93 """Send HID reports to the remote host.
94
95 Args:
96 data: the data to send
97 """
98 raise NotImplementedError('An HID subclass must override this method')
99
100
101class BluetoothHIDKeyboard(BluetoothHID):
102 """A bluetooth HID keyboard emulator class."""
103
104 def __init__(self, authentication_mode):
105 """Initialization of BluetoothHIDKeyboard
106
107 Args:
108 authentication_mode: the authentication mode
109 """
110 super(BluetoothHIDKeyboard, self).__init__(BluetoothHID.KEYBOARD,
111 authentication_mode)
112
113 def Send(self, data):
114 """Send data to the remote host.
115
116 Args:
117 data: data to send to the remote host
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800118 data could be either a string of printable ASCII characters or
119 a special key combination.
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800120 """
121 # TODO(josephsih): should have a method to check the connection status.
122 # Currently, once RN-42 is connected to a remote host, all characters
123 # except chr(0) transmitted through the serial port are interpreted
124 # as characters to send to the remote host.
125 # TODO(josephsih): Will support special keys and modifier keys soon.
126 # Currently, only printable ASCII characters are supported.
127 logging.debug('HID device sending %r...', data)
128 self.SerialSendReceive(data, msg='BluetoothHID.Send')
129 time.sleep(self.send_delay)
130
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800131 def SendKeyCombination(self, modifiers=None, keys=None):
132 """Send special key combinations to the remote host.
133
134 Args:
135 modifiers: a list of modifiers
136 keys: a list of scan codes of keys
137 """
138 press_codes = self.PressShorthandCodes(modifiers=modifiers, keys=keys)
139 release_codes = self.ReleaseShorthandCodes()
140 if press_codes and release_codes:
141 self.Send(press_codes)
142 self.Send(release_codes)
143 else:
144 logging.warn('modifers: %s and keys: %s are not valid', modifiers, keys)
145 return None
146
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800147
148def _UsageAndExit():
149 """The usage of this module."""
150 print 'Usage: python bluetooth_hid.py remote_address text_to_send'
151 print 'Example:'
152 print ' python bluetooth_hid.py 6C:29:95:1A:D4:6F "echo hello world"'
153 exit(1)
154
155
156def DemoBluetoothHIDKeyboard(remote_address, chars):
157 """A simple demo of acting as a HID keyboard.
158
159 This simple demo works only after the HID device has already paired
160 with the remote device such that a link key has been exchanged. Then
161 the HID device could connect directly to the remote host without
162 pin code and sends the message.
163
164 A full flow would be letting a remote host pair with the HID device
165 with the pin code of the HID device. Thereafter, either the host or
166 the HID device could request to connect. This is out of the scope of
167 this simple demo.
168
169 Args:
170 remote_address: the bluetooth address of the target remote device
171 chars: the characters to send
172 """
173 print 'Creating an emulated bluetooth keyboard...'
174 keyboard = BluetoothHIDKeyboard(BluetoothHID.PIN_CODE_MODE)
175
176 print 'Connecting to the remote address %s...' % remote_address
177 try:
178 if keyboard.ConnectToRemoteAddress(remote_address):
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800179 # Send printable ASCII strings a few times.
180 for i in range(1, 4):
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800181 print 'Sending "%s" for the %dth time...' % (chars, i)
182 keyboard.Send(chars + ' ' + str(i))
Joseph Hwangcb5623b2016-02-17 18:56:34 +0800183
184 # Demo special key combinations below.
185 print 'Create a new chrome tab.'
186 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL],
187 keys=[RN42.SCAN_T])
188
189 print 'Navigate to Google page.'
190 keyboard.Send('www.google.com')
191 time.sleep(1)
192
193 print 'Search hello world.'
194 keyboard.Send('hello world')
195 time.sleep(1)
196
197 print 'Navigate back to the previous page.'
198 keyboard.SendKeyCombination(keys=[RN42.SCAN_F1])
199 time.sleep(1)
200
201 print 'Switch to the previous tab.'
202 keyboard.SendKeyCombination(modifiers=[RN42.LEFT_CTRL, RN42.LEFT_SHIFT],
203 keys=[RN42.SCAN_TAB])
Joseph Hwang5d0dc642016-01-19 10:21:12 +0800204 else:
205 print 'Something is wrong. Not able to connect to the remote address.'
206 print 'Have you already paired RN-42 with the remote host?'
207 finally:
208 print 'Disconnecting...'
209 keyboard.Disconnect()
210
211 print 'Closing the keyboard...'
212 keyboard.Close()
213
214
215if __name__ == '__main__':
216 if len(sys.argv) != 3:
217 _UsageAndExit()
218
219 remote_host_address = sys.argv[1]
220 chars_to_send = sys.argv[2]
221
222 if len(remote_host_address.replace(':', '')) != 12:
223 print '"%s" is not a valid bluetooth address.' % remote_host_address
224 _UsageAndExit()
225
226 DemoBluetoothHIDKeyboard(remote_host_address, chars_to_send)