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