Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 1 | # Copyright 2019 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 | |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 5 | """This module provides an abstraction of the Nordic nRF52 BLE kit.""" |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 6 | |
| 7 | from __future__ import print_function |
| 8 | |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 9 | # TODO: to port chromite.lib.cros_logging to replace legacy logging |
| 10 | import logging # pylint: disable=cros-logging-import |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 11 | import time |
| 12 | |
Yu-Hsuan Hsu | 04cff0e | 2020-02-13 11:55:10 +0800 | [diff] [blame] | 13 | from .bluetooth_peripheral_kit import PeripheralKit |
| 14 | from .bluetooth_peripheral_kit import PeripheralKitException |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 15 | |
| 16 | |
| 17 | class nRF52Exception(PeripheralKitException): |
| 18 | """A dummy exception class for nRF52 class.""" |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 19 | |
| 20 | |
| 21 | class nRF52(PeripheralKit): |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 22 | """This is an abstraction of Nordic's nRF52 Dongle |
| 23 | |
| 24 | It is used to emulate BLE mouse and keyboard functionality. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 25 | |
| 26 | SDK: https://www.nordicsemi.com/Software-and-Tools/Software/nRF5-SDK |
| 27 | |
| 28 | See autotest-private/nRF52/ble_app_hids/README for information about |
| 29 | using the SDK to compile the application. |
| 30 | """ |
| 31 | |
| 32 | # Serial port settings (override) |
| 33 | BAUDRATE = 115200 |
| 34 | DRIVER = 'cdc_acm' |
| 35 | # Driver name in udev is 'cdc_acm', but builtin module is 'cdc-acm.ko' |
| 36 | # So we need to look for cdc_acm when searching by driver, |
| 37 | # but looking in builtins requires searching by 'cdc-acm'. |
| 38 | DRIVER_MODULE = 'cdc-acm' |
| 39 | BAUDRATE = 115200 |
Neeraj Poojary | 7a93d5a | 2019-07-01 13:56:10 -0700 | [diff] [blame] | 40 | USB_VID = '1366' |
| 41 | USB_PID = '1015' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 42 | |
| 43 | # A newline can just be a '\n' to denote the end of a command |
| 44 | NEWLINE = '\n' |
| 45 | CMD_FS = ' ' # Command field separator |
| 46 | |
| 47 | # Supported device types |
| 48 | MOUSE = 'MOUSE' |
| 49 | KEYBOARD = 'KEYBOARD' |
Yoni Shavit | 75d7cf1 | 2019-03-27 13:35:05 -0700 | [diff] [blame] | 50 | KNOWN_DEVICE_SET = None |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 51 | |
| 52 | RESET_SLEEP_SECS = 1 |
| 53 | |
| 54 | # Mouse button constants |
| 55 | MOUSE_BUTTON_LEFT_BIT = 1 |
| 56 | MOUSE_BUTTON_RIGHT_BIT = 2 |
| 57 | |
| 58 | # Specific Commands |
| 59 | # Reboot the nRF52 |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 60 | CMD_REBOOT = 'RBT' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 61 | # Reset the nRF52 and erase all previous bonds |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 62 | CMD_FACTORY_RESET = 'FRST' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 63 | # Return the name that is sent in advertisement packets |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 64 | CMD_GET_ADVERTISED_NAME = 'GN' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 65 | # Return the nRF52 firmware version |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 66 | CMD_GET_FIRMWARE_VERSION = 'GV' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 67 | # Return the Bluetooth address of the nRF52 |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 68 | CMD_GET_NRF52_MAC = 'GM' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 69 | # Return the address of the device connected (if there exists a connection) |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 70 | CMD_GET_REMOTE_CONNECTION_MAC = 'GC' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 71 | # Return the status of the nRF52's connection with a central device |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 72 | CMD_GET_CONNECTION_STATUS = 'GS' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 73 | |
| 74 | # Return the type of device the HID service is set |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 75 | CMD_GET_DEVICE_TYPE = 'GD' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 76 | # Set the nRF52 HID service to mouse |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 77 | CMD_SET_MOUSE = 'SM' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 78 | # Set the nRF52 HID service to keyboard |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 79 | CMD_SET_KEYBOARD = 'SK' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 80 | # Start HID service emulation |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 81 | CMD_START_HID_EM = 'START' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 82 | # Start HID service emulation |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 83 | CMD_STOP_HID_EM = 'STOP' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 84 | # Start advertising with the current settings (HID type) |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 85 | CMD_START_ADVERTISING = 'ADV' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 86 | |
| 87 | # Press (or clear) one or more buttons (left/right) |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 88 | CMD_MOUSE_BUTTON = 'B' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 89 | # Click the left and/or right button of the mouse |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 90 | CMD_MOUSE_CLICK = 'C' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 91 | # Move the mouse along x and/or y axis |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 92 | CMD_MOUSE_MOVE = 'M' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 93 | # Scrolling the mouse wheel up/down |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 94 | CMD_MOUSE_SCROLL = 'S' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 95 | |
| 96 | def GetCapabilities(self): |
| 97 | """What can this kit do/not do that tests need to adjust for? |
| 98 | |
| 99 | Returns: |
| 100 | A dictionary from PeripheralKit.CAP_* strings to an appropriate value. |
| 101 | See above (CAP_*) for details. |
| 102 | """ |
| 103 | return {PeripheralKit.CAP_TRANSPORTS: [PeripheralKit.TRANSPORT_LE], |
| 104 | PeripheralKit.CAP_HAS_PIN: False, |
| 105 | PeripheralKit.CAP_INIT_CONNECT: False} |
| 106 | |
| 107 | def EnterCommandMode(self): |
| 108 | """Make the kit enter command mode. |
| 109 | |
| 110 | The application on the nRF52 Dongle is always in command mode, so this |
| 111 | method will just create a serial connection if necessary |
| 112 | |
| 113 | Returns: |
| 114 | True if the kit successfully entered command mode. |
| 115 | |
| 116 | Raises: |
| 117 | nRF52Exception if there is an error in creating the serial connection |
| 118 | """ |
| 119 | if self._serial is None: |
| 120 | self.CreateSerialDevice() |
| 121 | if not self._command_mode: |
| 122 | self._command_mode = True |
| 123 | return True |
| 124 | |
| 125 | def LeaveCommandMode(self, force=False): |
| 126 | """Make the kit leave command mode. |
| 127 | |
| 128 | As above, the nRF52 application is always in command mode. |
| 129 | |
| 130 | Args: |
| 131 | force: True if we want to ignore potential errors and leave command mode |
| 132 | regardless of those errors |
| 133 | |
| 134 | Returns: |
| 135 | True if the kit successfully left command mode. |
| 136 | """ |
| 137 | if self._command_mode or force: |
| 138 | self._command_mode = False |
| 139 | return True |
| 140 | |
| 141 | def Reboot(self): |
| 142 | """Reboot the nRF52 Dongle. |
| 143 | |
| 144 | Does not erase the bond information. |
| 145 | |
| 146 | Returns: |
| 147 | True if the kit rebooted successfully. |
| 148 | """ |
| 149 | self.SerialSendReceive(self.CMD_REBOOT, |
| 150 | msg='rebooting nRF52') |
| 151 | time.sleep(self.RESET_SLEEP_SECS) |
| 152 | return True |
| 153 | |
| 154 | def FactoryReset(self): |
| 155 | """Factory reset the nRF52 Dongle. |
| 156 | |
| 157 | Erase the bond information and reboot. |
| 158 | |
| 159 | Returns: |
| 160 | True if the kit is reset successfully. |
| 161 | """ |
| 162 | self.SerialSendReceive(self.CMD_FACTORY_RESET, |
| 163 | msg='factory reset nRF52') |
| 164 | time.sleep(self.RESET_SLEEP_SECS) |
| 165 | return True |
| 166 | |
| 167 | def GetAdvertisedName(self): |
| 168 | """Get the name advertised by the nRF52. |
| 169 | |
| 170 | Returns: |
| 171 | The device name that the application uses in advertising |
| 172 | """ |
| 173 | return self.SerialSendReceive(self.CMD_GET_ADVERTISED_NAME, |
| 174 | msg='getting advertised name') |
| 175 | |
| 176 | def GetFirmwareVersion(self): |
| 177 | """Get the firmware version of the kit. |
| 178 | |
| 179 | This is useful for checking what features are supported if we want to |
| 180 | support muliple versions of some kit. |
| 181 | |
| 182 | For nRF52, returns the Link Layer Version (8 corresponds to BT 4.2), |
| 183 | Nordic Company ID (89), and Firmware ID (135). |
| 184 | |
| 185 | Returns: |
| 186 | The firmware version of the kit. |
| 187 | """ |
| 188 | return self.SerialSendReceive(self.CMD_GET_FIRMWARE_VERSION, |
| 189 | msg='getting firmware version') |
| 190 | |
| 191 | def GetOperationMode(self): |
| 192 | """Get the operation mode. |
| 193 | |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 194 | This is CENTRAL or PERIPHERAL for both BR/EDR and LE. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 195 | Not all kits may support all modes. |
| 196 | |
| 197 | nRF52 only supports peripheral role |
| 198 | |
| 199 | Returns: |
| 200 | The operation mode of the kit. |
| 201 | """ |
| 202 | logging.debug('GetOperationMode is a NOP on nRF52') |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 203 | return 'PERIPHERAL' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 204 | |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 205 | def SetCentralMode(self): |
| 206 | """Set the kit to central mode. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 207 | |
| 208 | nRF52 application only acts as a peripheral |
| 209 | |
| 210 | Returns: |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 211 | True if central mode was set successfully. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 212 | |
| 213 | Raises: |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 214 | A kit-specific exception if central mode is unsupported. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 215 | """ |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 216 | error_msg = 'Failed to set central mode' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 217 | logging.error(error_msg) |
| 218 | raise nRF52Exception(error_msg) |
| 219 | |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 220 | def SetPeripheralMode(self): |
| 221 | """Set the kit to peripheral mode. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 222 | |
| 223 | Silently succeeds, because the nRF52 application is always a peripheral |
| 224 | |
| 225 | Returns: |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 226 | True if peripheral mode was set successfully. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 227 | |
| 228 | Raises: |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 229 | A kit-specific exception if peripheral mode is unsupported. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 230 | """ |
Archie Pusaka | 8b872a7 | 2021-04-13 14:59:50 +0800 | [diff] [blame^] | 231 | logging.debug('SetPeripheralMode is a NOP on nRF52') |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 232 | return True |
| 233 | |
| 234 | def GetAuthenticationMode(self): |
| 235 | """Get the authentication mode. |
| 236 | |
| 237 | This specifies how the device will authenticate with the DUT, for example, |
| 238 | a PIN code may be used. |
| 239 | |
| 240 | Not supported on nRF52 application. |
| 241 | |
| 242 | Returns: |
| 243 | None as the nRF52 does not support an Authentication mode. |
| 244 | """ |
| 245 | logging.debug('GetAuthenticationMode is a NOP on nRF52') |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 246 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 247 | |
| 248 | def SetAuthenticationMode(self, mode): |
| 249 | """Set the authentication mode to the specified mode. |
| 250 | |
| 251 | If mode is PIN_CODE_MODE, implementations must ensure the default PIN |
| 252 | is set by calling _SetDefaultPinCode() as appropriate. |
| 253 | |
| 254 | Not supported on nRF52 application. |
| 255 | |
| 256 | Args: |
| 257 | mode: the desired authentication mode (specified in PeripheralKit) |
| 258 | |
| 259 | Returns: |
| 260 | True if the mode was set successfully, |
| 261 | |
| 262 | Raises: |
| 263 | A kit-specific exception if given mode is not supported. |
| 264 | """ |
| 265 | error_msg = 'nRF52 does not support authentication mode' |
| 266 | logging.error(error_msg) |
| 267 | raise nRF52Exception(error_msg) |
| 268 | |
| 269 | def GetPinCode(self): |
| 270 | """Get the pin code. |
| 271 | |
| 272 | Returns: |
| 273 | A string representing the pin code, |
| 274 | None if there is no pin code stored. |
| 275 | """ |
| 276 | warn_msg = 'nRF52 does not support PIN code mode, no PIN exists' |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 277 | logging.warning(warn_msg) |
| 278 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 279 | |
| 280 | def SetPinCode(self, pin): |
| 281 | """Set the pin code. |
| 282 | |
| 283 | Not support on nRF52 application. |
| 284 | |
| 285 | Returns: |
| 286 | True if the pin code is set successfully, |
| 287 | |
| 288 | Raises: |
| 289 | A kit-specifc exception if the pin code is invalid. |
| 290 | """ |
| 291 | error_msg = 'nRF52 does not support PIN code mode' |
| 292 | logging.error(error_msg) |
| 293 | raise nRF52Exception(error_msg) |
| 294 | |
| 295 | def GetServiceProfile(self): |
| 296 | """Get the service profile. |
| 297 | |
| 298 | Unrelated to HID for the nRF52 application, so ignore for now |
| 299 | |
| 300 | Returns: |
| 301 | The service profile currently in use (as per constant in PeripheralKit) |
| 302 | """ |
| 303 | logging.debug('GetServiceProfile is a NOP on nRF52') |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 304 | return 'HID' |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 305 | |
| 306 | def SetServiceProfileSPP(self): |
| 307 | """Set SPP as the service profile. |
| 308 | |
| 309 | Unrelated to HID for the nRF52 application, so ignore for now |
| 310 | |
| 311 | Returns: |
| 312 | True if the service profile was set to SPP successfully. |
| 313 | |
| 314 | Raises: |
| 315 | A kit-specifc exception if unsuppported. |
| 316 | """ |
| 317 | error_msg = 'Failed to set SPP service profile' |
| 318 | logging.error(error_msg) |
| 319 | raise nRF52Exception(error_msg) |
| 320 | |
| 321 | def SetServiceProfileHID(self): |
| 322 | """Set HID as the service profile. |
| 323 | |
| 324 | nRF52 application only does HID at the moment. Silently succeeds |
| 325 | |
| 326 | Returns: |
| 327 | True if the service profile was set to HID successfully. |
| 328 | """ |
| 329 | logging.debug('SetServiceProfileHID is a NOP on nRF52') |
| 330 | return True |
| 331 | |
| 332 | def GetLocalBluetoothAddress(self): |
| 333 | """Get the address advertised by the nRF52, which is the MAC address. |
| 334 | |
| 335 | Address is returned as XX:XX:XX:XX:XX:XX |
| 336 | |
| 337 | Returns: |
| 338 | The address of the nRF52 if successful or None if it fails |
| 339 | """ |
| 340 | address = self.SerialSendReceive(self.CMD_GET_NRF52_MAC, |
| 341 | msg='getting local MAC address') |
| 342 | return address |
| 343 | |
| 344 | def GetRemoteConnectedBluetoothAddress(self): |
| 345 | """Get the address of the device that is connected to the nRF52. |
| 346 | |
| 347 | Address is returned as XX:XX:XX:XX:XX:XX |
| 348 | If not connected, nRF52 will return 00:00:00:00:00:00 |
| 349 | |
| 350 | Returns: |
| 351 | The address of the connected device or a null address if successful. |
| 352 | None if the serial receiving fails |
| 353 | """ |
| 354 | address = self.SerialSendReceive(self.CMD_GET_REMOTE_CONNECTION_MAC, |
| 355 | msg='getting remote MAC address') |
| 356 | if len(address) == 17: |
| 357 | return address |
| 358 | else: |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 359 | logging.error('remote connection address is invalid: %s', address) |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 360 | return None |
| 361 | |
| 362 | def GetConnectionStatus(self): |
| 363 | """Get whether the nRF52 is connected to another device. |
| 364 | |
| 365 | nRF52 returns a string 'INVALID' or 'CONNECTED' |
| 366 | |
| 367 | Returns: |
| 368 | True if the nRF52 is connected to another device |
| 369 | """ |
| 370 | result = self.SerialSendReceive(self.CMD_GET_CONNECTION_STATUS, |
| 371 | msg = 'getting connection status') |
| 372 | return result == 'CONNECTED' |
| 373 | |
| 374 | def EnableConnectionStatusMessage(self): |
| 375 | """Enable the connection status message. |
| 376 | |
| 377 | On some kits, this is required to use connection-related methods. |
| 378 | |
| 379 | Not supported by the nRF52 application for now. This could be |
| 380 | changed so that Connection Status Messages are sent by nRF52. |
| 381 | |
| 382 | Returns: |
| 383 | True if enabling the connection status message successfully. |
| 384 | """ |
| 385 | logging.debug('EnableConnectionStatusMessage is a NOP on nRF52') |
| 386 | return True |
| 387 | |
| 388 | def DisableConnectionStatusMessage(self): |
| 389 | """Disable the connection status message. |
| 390 | |
| 391 | Not supported by the nRF52 application for now. This could be |
| 392 | changed so that Connection Status Messages are sent by nRF52. |
| 393 | |
| 394 | Returns: |
| 395 | True if disabling the connection status message successfully. |
| 396 | """ |
| 397 | logging.debug('DisableConnectionStatusMessage is a NOP on nRF52') |
| 398 | return True |
| 399 | |
Joseph Hwang | 1414086 | 2020-01-10 16:25:02 +0800 | [diff] [blame] | 400 | def GetDeviceType(self): |
| 401 | """Get the device type. |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 402 | |
| 403 | Returns: |
Joseph Hwang | 1414086 | 2020-01-10 16:25:02 +0800 | [diff] [blame] | 404 | A string representing the device type |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 405 | """ |
| 406 | return self.SerialSendReceive(self.CMD_GET_DEVICE_TYPE, |
Joseph Hwang | 1414086 | 2020-01-10 16:25:02 +0800 | [diff] [blame] | 407 | msg='getting the device type') |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 408 | |
| 409 | def SetHIDType(self, device_type): |
| 410 | """Set HID type to the specified device type. |
| 411 | |
| 412 | Args: |
| 413 | device_type: the HID type to emulate, from PeripheralKit |
| 414 | (MOUSE, KEYBOARD) |
| 415 | |
| 416 | Returns: |
| 417 | True if successful |
| 418 | |
| 419 | Raises: |
| 420 | A kit-specific exception if that device type is not supported. |
| 421 | """ |
| 422 | if device_type == self.MOUSE: |
| 423 | result = self.SerialSendReceive(self.CMD_SET_MOUSE, |
| 424 | msg='setting mouse as HID type') |
| 425 | print(result) |
| 426 | elif device_type == self.KEYBOARD: |
| 427 | self.SerialSendReceive(self.CMD_SET_KEYBOARD, |
| 428 | msg='setting keyboard as HID type') |
| 429 | else: |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 430 | msg = 'Failed to set HID type, not supported: %s' % device_type |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 431 | logging.error(msg) |
| 432 | raise nRF52Exception(msg) |
| 433 | return True |
| 434 | |
| 435 | def GetClassOfService(self): |
| 436 | """Get the class of service, if supported. |
| 437 | |
| 438 | Not supported on nRF52 |
| 439 | |
| 440 | Returns: |
| 441 | None, the only reasonable value for BLE-only devices |
| 442 | """ |
| 443 | logging.debug('GetClassOfService is a NOP on nRF52') |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 444 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 445 | |
| 446 | def SetClassOfService(self, class_of_service): |
| 447 | """Set the class of service, if supported. |
| 448 | |
| 449 | The class of service is a number usually assigned by the Bluetooth SIG. |
| 450 | Usually supported only on BR/EDR kits. |
| 451 | |
| 452 | Not supported on nRF52, but fake it |
| 453 | |
| 454 | Args: |
| 455 | class_of_service: A decimal integer representing the class of service. |
| 456 | |
| 457 | Returns: |
| 458 | True as this action is not supported. |
| 459 | """ |
| 460 | logging.debug('SetClassOfService is a NOP on nRF52') |
| 461 | return True |
| 462 | |
| 463 | def GetClassOfDevice(self): |
| 464 | """Get the class of device, if supported. |
| 465 | |
| 466 | The kit uses a hexadeciaml string to represent the class of device. |
| 467 | It is converted to a decimal number as the return value. |
| 468 | The class of device is a number usually assigned by the Bluetooth SIG. |
| 469 | Usually supported only on BR/EDR kits. |
| 470 | |
| 471 | Not supported on nRF52, so None |
| 472 | |
| 473 | Returns: |
| 474 | None, the only reasonable value for BLE-only devices. |
| 475 | """ |
| 476 | logging.debug('GetClassOfDevice is a NOP on nRF52') |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 477 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 478 | |
| 479 | def SetClassOfDevice(self, device_type): |
| 480 | """Set the class of device, if supported. |
| 481 | |
| 482 | The class of device is a number usually assigned by the Bluetooth SIG. |
| 483 | Usually supported only on BR/EDR kits. |
| 484 | |
| 485 | Not supported on nRF52, but fake it. |
| 486 | |
| 487 | Args: |
| 488 | device_type: A decimal integer representing the class of device. |
| 489 | |
| 490 | Returns: |
| 491 | True as this action is not supported. |
| 492 | """ |
| 493 | logging.debug('SetClassOfDevice is a NOP on nRF52') |
| 494 | return True |
| 495 | |
| 496 | def SetRemoteAddress(self, remote_address): |
| 497 | """Set the remote Bluetooth address. |
| 498 | |
| 499 | (Usually this will be the device under test that we want to connect with, |
| 500 | where the kit starts the connection.) |
| 501 | |
| 502 | Not supported on nRF52 HID application. |
| 503 | |
| 504 | Args: |
| 505 | remote_address: the remote Bluetooth MAC address, which must be given as |
| 506 | 12 hex digits with colons between each pair. |
| 507 | For reference: '00:29:95:1A:D4:6F' |
| 508 | |
| 509 | Returns: |
| 510 | True if the remote address was set successfully. |
| 511 | |
| 512 | Raises: |
| 513 | PeripheralKitException if the given address was malformed. |
| 514 | """ |
| 515 | error_msg = 'Failed to set remote address' |
| 516 | logging.error(error_msg) |
| 517 | raise nRF52Exception(error_msg) |
| 518 | |
| 519 | def Connect(self): |
| 520 | """Connect to the stored remote bluetooth address. |
| 521 | |
| 522 | In the case of a timeout (or a failure causing an exception), the caller |
| 523 | is responsible for retrying when appropriate. |
| 524 | |
| 525 | Not supported on nRF52 HID application. |
| 526 | |
| 527 | Returns: |
| 528 | True if connecting to the stored remote address succeeded, or |
| 529 | False if a timeout occurs. |
| 530 | """ |
| 531 | error_msg = 'Failed to connect to remote device' |
| 532 | logging.error(error_msg) |
| 533 | raise nRF52Exception(error_msg) |
| 534 | |
| 535 | def Disconnect(self): |
| 536 | """Disconnect from the remote device. |
| 537 | |
| 538 | Specifically, this causes the peripheral emulation kit to disconnect from |
| 539 | the remote connected device, usually the DUT. |
| 540 | |
| 541 | Returns: |
| 542 | True if disconnecting from the remote device succeeded. |
| 543 | """ |
| 544 | self.SerialSendReceive(self.CMD_DISCONNECT, |
| 545 | msg='disconnect') |
| 546 | return True |
| 547 | |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 548 | def Discover(self, remote_address): |
| 549 | """Discover the remote address. |
| 550 | |
| 551 | Not supported on nRF52 HID application. |
| 552 | |
| 553 | Args: |
| 554 | remote_address: the remote Bluetooth address, which must be given as 12 |
| 555 | hex digits with colons between each pair. |
| 556 | For reference: '00:29:95:1A:D4:6F' |
| 557 | |
| 558 | Returns: |
| 559 | True if discovering the remote address succeeded |
| 560 | """ |
| 561 | error_msg = 'nRF52 does not support discovery' |
| 562 | logging.error(error_msg) |
| 563 | raise nRF52Exception(error_msg) |
| 564 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 565 | def StartAdvertising(self): |
| 566 | """Command the nRF52 to begin advertising with its current settings. |
| 567 | |
| 568 | Returns: |
| 569 | True if successful. |
| 570 | """ |
| 571 | self.SerialSendReceive(self.CMD_START_ADVERTISING, |
| 572 | msg='start advertising') |
| 573 | return True |
| 574 | |
| 575 | def MouseMove(self, delta_x, delta_y): |
| 576 | """Move the mouse (delta_x, delta_y) steps. |
| 577 | |
| 578 | Buttons currently pressed will stay pressed during this operation. |
| 579 | This move is relative to the current position by the HID standard. |
| 580 | Valid step values must be in the range [-127,127]. |
| 581 | |
| 582 | Args: |
| 583 | delta_x: The number of steps to move horizontally. |
| 584 | Negative values move left, positive values move right. |
| 585 | delta_y: The number of steps to move vertically. |
| 586 | Negative values move up, positive values move down. |
| 587 | |
| 588 | Returns: |
| 589 | True if successful. |
| 590 | """ |
| 591 | command = self.CMD_MOUSE_MOVE + self.CMD_FS |
| 592 | command += str(delta_x) + self.CMD_FS + str(delta_y) |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 593 | message = 'moving BLE mouse ' + str(delta_x) + ' ' + str(delta_y) |
| 594 | self.SerialSendReceive(command, msg=message) |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 595 | return True |
| 596 | |
| 597 | def MouseScroll(self, steps): |
| 598 | """Scroll the mouse wheel steps number of steps. |
| 599 | |
| 600 | Buttons currently pressed will stay pressed during this operation. |
| 601 | Valid step values must be in the range [-127,127]. |
| 602 | |
| 603 | Args: |
| 604 | steps: The number of steps to scroll the wheel. |
| 605 | With traditional scrolling: |
| 606 | Negative values scroll down, positive values scroll up. |
| 607 | With reversed (formerly "Australian") scrolling this is reversed. |
| 608 | |
| 609 | Returns: |
| 610 | True if successful. |
| 611 | """ |
| 612 | command = self.CMD_MOUSE_SCROLL + self.CMD_FS |
| 613 | command += self.CMD_FS |
| 614 | command += str(steps) + self.CMD_FS |
| 615 | message = 'scrolling BLE mouse' |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 616 | self.SerialSendReceive(command, msg=message) |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 617 | return True |
| 618 | |
| 619 | def MouseHorizontalScroll(self, steps): |
| 620 | """Horizontally scroll the mouse wheel steps number of steps. |
| 621 | |
| 622 | Buttons currently pressed will stay pressed during this operation. |
| 623 | Valid step values must be in the range [-127,127]. |
| 624 | |
| 625 | There is no nRF52 limitation for implementation. If we can program |
| 626 | the correct HID event report to emulate horizontal scrolling, this |
| 627 | can be supported. |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 628 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 629 | **** Not implemented **** |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 630 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 631 | Args: |
| 632 | steps: The number of steps to scroll the wheel. |
| 633 | With traditional scrolling: |
| 634 | Negative values scroll left, positive values scroll right. |
| 635 | With reversed (formerly "Australian") scrolling this is reversed. |
| 636 | |
| 637 | Returns: |
| 638 | True if successful. |
| 639 | """ |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 640 | |
| 641 | del steps # to silent linter warning |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 642 | return True |
| 643 | |
| 644 | def _MouseButtonCodes(self): |
| 645 | """Gives the letter codes for whatever buttons are pressed. |
| 646 | |
| 647 | Returns: |
| 648 | A int w/ bits representing pressed buttons. |
| 649 | """ |
| 650 | currently_pressed = 0 |
| 651 | for button in self._buttons_pressed: |
| 652 | if button == PeripheralKit.MOUSE_BUTTON_LEFT: |
| 653 | currently_pressed += self.MOUSE_BUTTON_LEFT_BIT |
| 654 | elif button == PeripheralKit.MOUSE_BUTTON_RIGHT: |
| 655 | currently_pressed += self.MOUSE_BUTTON_RIGHT_BIT |
| 656 | else: |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 657 | error = 'Unknown mouse button in state: %s' % button |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 658 | logging.error(error) |
| 659 | raise nRF52Exception(error) |
| 660 | return currently_pressed |
| 661 | |
| 662 | def MousePressButtons(self, buttons): |
| 663 | """Press the specified mouse buttons. |
| 664 | |
| 665 | The kit will continue to press these buttons until otherwise instructed, or |
| 666 | until its state has been reset. |
| 667 | |
| 668 | Args: |
| 669 | buttons: A set of buttons, as PeripheralKit MOUSE_BUTTON_* values, that |
| 670 | will be pressed (and held down). |
| 671 | |
| 672 | Returns: |
| 673 | True if successful. |
| 674 | """ |
| 675 | self._MouseButtonStateUnion(buttons) |
| 676 | button_codes = self._MouseButtonCodes() |
| 677 | command = self.CMD_MOUSE_BUTTON + self.CMD_FS |
| 678 | command += str(button_codes) |
| 679 | message = 'pressing BLE mouse buttons' |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 680 | self.SerialSendReceive(command, msg=message) |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 681 | return True |
| 682 | |
| 683 | def MouseReleaseAllButtons(self): |
| 684 | """Release all mouse buttons. |
| 685 | |
| 686 | Returns: |
| 687 | True if successful. |
| 688 | """ |
| 689 | self._MouseButtonStateClear() |
| 690 | command = self.CMD_MOUSE_BUTTON + self.CMD_FS |
| 691 | command += '0' |
| 692 | message = 'releasing all BLE HOG mouse buttons' |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 693 | self.SerialSendReceive(command, msg=message) |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 694 | return True |
| 695 | |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 696 | def SetDiscoverable(self, discoverable): |
| 697 | """Sets the discoverability of the device. |
| 698 | |
| 699 | Not supported on nRF52 application. |
| 700 | |
| 701 | Args: |
| 702 | discoverable: Whether device is discoverable/advertising. |
| 703 | """ |
| 704 | error_msg = 'nRF52 does not support discoverable' |
| 705 | logging.error(error_msg) |
| 706 | raise nRF52Exception(error_msg) |
| 707 | |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 708 | def Reset(self): |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 709 | self.SerialSendReceive(nRF52.CMD_REBOOT, msg='reset nRF52') |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 710 | return True |
| 711 | |
| 712 | def SetModeMouse(self): |
| 713 | self.EnterCommandMode() |
Archie Pusaka | 3c9fd0c | 2021-04-13 15:26:46 +0800 | [diff] [blame] | 714 | self.SerialSendReceive(nRF52.CMD_SET_MOUSE, msg='set nRF52 mouse') |
Neeraj Poojary | 59f8b15 | 2019-02-01 14:15:57 -0800 | [diff] [blame] | 715 | return True |
| 716 | |
| 717 | def GetKitInfo(self, connect_separately=False, test_reset=False): |
| 718 | """A simple demo of getting kit information.""" |
| 719 | if connect_separately: |
| 720 | print('create serial device: %s' % self.CreateSerialDevice()) |
| 721 | if test_reset: |
| 722 | print('factory reset: %s' % self.FactoryReset()) |
| 723 | self.EnterCommandMode() |
| 724 | print('advertised name: %s' % self.GetAdvertisedName()) |
| 725 | print('firmware version: %s' % self.GetFirmwareVersion()) |
| 726 | print('local bluetooth address: %s' % self.GetLocalBluetoothAddress()) |
| 727 | print('connection status: %s' % self.GetConnectionStatus()) |
| 728 | # The class of service/device is None for LE kits (it is BR/EDR-only) |
| 729 | |
| 730 | |
| 731 | if __name__ == '__main__': |
| 732 | kit_instance = nRF52() |
| 733 | kit_instance.GetKitInfo() |