From 5ad43b05fd7dfafc6d63c1b478a64759962d6b6d Mon Sep 17 00:00:00 2001 From: Mike Wadsten Date: Wed, 16 Sep 2020 15:02:27 -0500 Subject: [PATCH] Release new typehints for XBee (3) Cellular x16 firmware --- samples/bluetooth/pairing/README.md | 83 ++++++++++++++ samples/bluetooth/pairing/main.py | 103 +++++++++++++++++ samples/bluetooth/xbee_connect/README.md | 78 +++++++++++++ samples/bluetooth/xbee_connect/main.py | 64 +++++++++++ typehints/common/digi/ble.pyi | 138 +++++++++++++++++++++-- 5 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 samples/bluetooth/pairing/README.md create mode 100644 samples/bluetooth/pairing/main.py create mode 100644 samples/bluetooth/xbee_connect/README.md create mode 100644 samples/bluetooth/xbee_connect/main.py diff --git a/samples/bluetooth/pairing/README.md b/samples/bluetooth/pairing/README.md new file mode 100644 index 0000000..4f688d2 --- /dev/null +++ b/samples/bluetooth/pairing/README.md @@ -0,0 +1,83 @@ +Bluetooth Pairing Sample Application +============================================================ + +This example demonstrates performing pairing to secure the BLE +connection to another BLE device. + +Requirements +------------ + +To run this example you need: + +* One XBee3 radio module with MicroPython support. +* One carrier board for the radio module (XBIB-U-DEV or XBIB-C board). +* A peripheral BLE device to connect to which supports pairing. + +Setup +----- + +Make sure the hardware is set up correctly: + +1. Plug the XBee3 radio module into the XBee adapter and connect it to your + computer's USB port. +2. Find the BLE MAC address of the peripheral BLE device. It will be + used later in the example. The peripheral must support pairing with + MITM support. If it does not, edit the `ble.config` like to remove + that flag. If the flag is removed Just Works pairing may occur with + no callbacks. + +Run +--- + +Before launching the application, update the `REMOTE_ADDRESS` and `address_type` +variables in `main.py` to match your BLE peripheral device. + +Compile and launch the MicroPython application. It prints information to the +console. + +After connecting to the peripheral device, it will delay for a short +period of time and then one of the `io_callback` methods will be +called. The specific callback will depend on the capabilities of the +BLE peripheral being paired against. + +Example session when connecting to a device with a keyboard and display. + + Loading /flash/main.mpy... + Running bytecode... + Connecting + Connected + Wait for a bit before securing + Securing + Sleep forever + The passkey is 520308 + Is this correct (y/n): y + Secured + +Supported platforms +------------------- + +* Digi XBee3 Cellular LTE-M/NB-IoT - minimum firmware version: 11416 +* Digi XBee3 Cellular LTE Cat 1 - minimum firmware version: x16 + +License +------- + +Copyright (c) 2020, Digi International Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/samples/bluetooth/pairing/main.py b/samples/bluetooth/pairing/main.py new file mode 100644 index 0000000..0839465 --- /dev/null +++ b/samples/bluetooth/pairing/main.py @@ -0,0 +1,103 @@ +# Copyright (c) 2020, Digi International Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Change these two variables to your device's address and address type. +# The address and address type can be discovered using ble.gap_scan(). + +import binascii +import time + +from digi import ble + +REMOTE_ADDRESS = "f2:bc:3c:06:01:0a" +address_type = ble.ADDR_TYPE_PUBLIC + +# Put address into bytes (without colons) +address = binascii.unhexlify(REMOTE_ADDRESS.replace(':', '')) + + +# Present the passkey to the user +def display_cb(passkey): + print("The passkey is {}".format(passkey)) + + +# Solicit the passkey from the user. +# NOTE: `ble.passkey_enter()` does not need to be called from the +# callback, this is done for simplicity here, but blocking the +# callback for user activity in a real application may not be +# desirable. +def request_cb(): + print("The passkey is being requested") + passkey = input("Passkey: ") + passkey = int(passkey) + ble.passkey_enter(passkey) + + +# Ask the user to confirm the passkey +# NOTE: As above `passkey_confirm` need not be called from the +# callback. +def confirm_cb(passkey): + print("The passkey is {}".format(passkey)) + yn = input("Is this correct (y/n): ") + yn = yn.strip().lower() + ble.passkey_confirm(yn[0] == 'y') + + +# Called when the `secure` operation completes +def secure_cb(code): + if code == 0: + print("Secured") + else: + print("Pairing failed with error 0x{:x}".format(code)) + + +def main(): + # io_callbacks must be called before `ble.config` to enable + # Require MITM. + ble.io_callbacks(display_cb=display_cb, + request_cb=request_cb, + confirm_cb=confirm_cb) + ble.config(security=ble.PAIRING_REQUIRE_MITM) + # Comment the line above, and uncomment the line below to use + # bonding. Once bonded the pairing activity will no longer be + # necessary on each connection as long as the keys are retained by + # the devices. + # ble.config(security=ble.PAIRING_REQUIRE_MITM | ble.PAIRING_REQUIRE_BONDING) + + + print("Connecting") + conn = ble.gap_connect(address_type, address) + print("Connected") + + # The delay is not necessary, just here to easily observe the + # secured vs unsecured state. + print("Wait for a bit before securing") + time.sleep(5) + + print("Securing") + conn.secure(secure_cb) + + print("Sleep forever") + while True: + time.sleep(1) + + +if __name__ == "__main__": + main() diff --git a/samples/bluetooth/xbee_connect/README.md b/samples/bluetooth/xbee_connect/README.md new file mode 100644 index 0000000..201c025 --- /dev/null +++ b/samples/bluetooth/xbee_connect/README.md @@ -0,0 +1,78 @@ +XBee-to-XBee Bluetooth Connection Sample Application +====================================== + +This example demonstrates XBee to XBee communication by creating an +authenticated and encrypted connection to the API service of a remote +XBee 3 device. + +When run the example will connect to the specified XBee address with +the provided password and periodically query the time and temperature +of the remote XBee. + +Requirements +------------ + +To run this example you need: + +* Two XBee3 radio modules with MicroPython support. + * One will act as the MicroPython host, the other the target for + the API Service client. +* Two carrier boards for the radio module (XBIB-U-DEV or XBIB-C board). + +Setup +----- + +Make sure the hardware is set up correctly: + +1. Plug the XBee3 radio modules into the XBee adapters and connect to + your computer's USB port. +2. On the XBee3 that will act as the API Service server + 1. Enable Bluetooth + 2. Configure a password and take note of the Bluetooth address and + password for customization of the MicroPython application. + +Run +--- + +The example must be configured prior to execution by providing the +Bluetooth address and the password for the server XBee3. The example +code should be modified to provide these values in the ADDRESS and +PASSWORD variables at the top of the file. + +When run the script reports when it performs a query and provides +interpreted response data for the time and temperature when it is +received. + +Required libraries +-------------------- + +N/A + +Supported platforms +------------------- + +* Digi XBee3 Cellular LTE-M/NB-IoT - minimum firmware version: 11416 +* Digi XBee3 Cellular LTE Cat 1 - minimum firmware version: x16 + +License +------- + +Copyright (c) 2020, Digi International, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/samples/bluetooth/xbee_connect/main.py b/samples/bluetooth/xbee_connect/main.py new file mode 100644 index 0000000..8ccfb14 --- /dev/null +++ b/samples/bluetooth/xbee_connect/main.py @@ -0,0 +1,64 @@ +# Copyright (c) 2020, Digi International Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import time +import struct +from binascii import unhexlify + +from digi import ble +ble.active(True) + +# Replace these with the BL and password values for your XBee +ADDRESS = "90FD9F7B764B" +PASSWORD = "password" + +DT_request = unhexlify("7E000508014454015D") +TP_request = unhexlify("7E00040801545052") + + +def process_at_cmd_response(payload): + frame_id, at_cmd, status = struct.unpack(">B2sB", payload[:4]) + value = payload[4:] + if at_cmd == b"DT": + print("Peer responded with time: {}".format(value)) + elif at_cmd == b"TP": + (temp,) = struct.unpack(">H", value) + print("Peer responded with temperature: {}".format(temp)) + + +def process_frame(frame): + delim, len, cmd = struct.unpack(">BHB", frame[:4]) # Throw away initial delimiter + payload = frame[4:-1] # Throw away CRC, checked by receive logic + + if cmd == 0x88: + process_at_cmd_response(payload) + + +def main(): + conn = ble.gap_connect(ble.ADDR_TYPE_PUBLIC, unhexlify(ADDRESS)) + xbeeconn = ble.xbee_connect(conn, process_frame, PASSWORD, timeout=10) + + while True: + print("Querying DT & TP") + xbeeconn.send(DT_request) + xbeeconn.send(TP_request) + time.sleep(5) + + +main() diff --git a/typehints/common/digi/ble.pyi b/typehints/common/digi/ble.pyi index 80a9786..b79e683 100644 --- a/typehints/common/digi/ble.pyi +++ b/typehints/common/digi/ble.pyi @@ -73,12 +73,13 @@ _DescriptorTuple = Tuple[_DescriptorHandle, 'UUID'] __all__ = ( - 'ADDR_TYPE_PUBLIC', 'ADDR_TYPE_RANDOM', 'ADDR_TYPE_PUBLIC_IDENTITY', 'ADDR_TYPE_RANDOM_IDENTITY', - 'PROP_BROADCAST', 'PROP_READ', 'PROP_WRITE', 'PROP_WRITE_NO_RESP', 'PROP_AUTH_SIGNED_WR', - 'PROP_NOTIFY', 'PROP_INDICATE', - 'active', 'config', 'gap_advertise', 'gap_scan', 'gap_connect', - 'UUID', -) + 'ADDR_TYPE_PUBLIC', 'ADDR_TYPE_RANDOM', 'ADDR_TYPE_PUBLIC_IDENTITY', + 'ADDR_TYPE_RANDOM_IDENTITY', 'PROP_BROADCAST', 'PROP_READ', + 'PROP_WRITE', 'PROP_WRITE_NO_RESP', 'PROP_AUTH_SIGNED_WR', + 'PROP_NOTIFY', 'PROP_INDICATE', 'PAIRING_REQUIRE_MITM', + 'PAIRING_REQUIRE_BONDING', 'PAIRING_DISABLE_LEGACY' 'active', + 'config', 'gap_advertise', 'gap_scan', 'gap_connect', + 'xbee_connect' 'UUID', ) ADDR_TYPE_PUBLIC: int = ... @@ -94,6 +95,10 @@ PROP_AUTH_SIGNED_WR: int = ... PROP_NOTIFY: int = ... PROP_INDICATE: int = ... +PAIRING_REQUIRE_MITM: int = ... +PAIRING_REQUIRE_BONDING: int = ... +PAIRING_DISABLE_LEGACY: int = ... + def active( active: Optional[bool] = None, @@ -910,7 +915,25 @@ class _gap_connect(ContextManager): """ ... - def __enter__(self) -> _gap_connect: + def secure(self, cb: Callable[[int], None], /) -> None: + """Performs pairing/bonding on a connection. + + :param cb: A callback which will be called upon completion of + the pairing operation. It will be passed the value zero if + the pairing succeeded, otherwise it will be passed a BLE + error as documented in the Bluetooth Core spec.. + + See also the ``security`` argument to ``ble.config`` to guide + the behavior of the pairing/bonding operation. + + The ``secure`` function is new in the following firmware versions: + * XBee3 Cellular LTE-M/NB-IoT: version 11416 + * XBee3 Cellular LTE Cat 1: version x16 + + """ + ... + + def __enter__(self) -> _gap_connect: """ Enter the runtime context for using this GAP connection object as a context manager (using the ``with`` statement). @@ -1067,3 +1090,104 @@ def gap_connect( """ ... + + +class _xbee_connect(): + """ + Class used to encapsulate an encrypted connection to a remote XBee3 API Service. + + This class cannot be instantiated, it is returned from ``digi.ble.xbee_connect()``. + + See the documentation for ``digi.ble.xbee_connect()`` for examples of usage. + """ + def send(self, data: bytes) -> None: + """Send 'data' to the API Service of the peer. + + 'data' should be valid API frames (including delimiter and + checksum). Responses, if any, will be made through callbacks + to the 'receive' argument used in the initial + 'digi.ble.xbee_connect()' call. + + """ + ... + + +def xbee_connect(conn: _gap_connect, + receive: Callable[[bytes], None], + password: str, + timeout: int) -> _xbee_connect: + """Create an authenticated and encrypted connection to XBee3 API Service. + + This method will discover the API Service on the peer connection + 'conn' and authenticate using the provided password. + + Returns an object that allows encrypted communication using the + state established by performing SRP. + + """ + ... + + +def delete_bondings() -> None: + """Remove all stored bonding table entries.""" + ... + + +PasskeyCB = Callable[[int], None] +RequestCB = Callable[None, None] + + +def io_callbacks(display_cb: PasskeyCB, + confirm_cb: PasskeyCB, + request_cb: RequestCB + ) -> None: + """Provide callbacks which define IO capabilities for pairing. + + :param display_cb: Callback to be used to present a passkey to the user. + + :param confirm_cb: Callback to be used when the user must + confirm (Y/N) a passkey. The passkey will be provided as + an argument. The passkey should be presented to the user + and the users input should be fed back using + ``ble.passkey_confirm``. + + :param request_cb: Callback to be indicate that the user must + input a passkey value. The use should be prompted to enter + a passkey and the passkey provided by the user should be + fed back using ``ble.passkey_enter``. + + NOTE: The BLE standards recommend that the passkey be + presented to the user as a six digit number padded with + leading zeros. + + The ``io_callbacks`` function is new in the following firmware versions: + * XBee3 Cellular LTE-M/NB-IoT: version 11416 + * XBee3 Cellular LTE Cat 1: version x16 + """ + ... + + +def passkey_confirm(confirmation: bool, /) -> None: + """Allows user confirmation of BLE pairing passkey. + + :param confirmation: Provide True if the passkey provided by + the ``confirm_cb`` is correct, False otherwise. + + The ``passkey_confirm`` function is new in the following firmware versions: + * XBee3 Cellular LTE-M/NB-IoT: version 11416 + * XBee3 Cellular LTE Cat 1: version x16 + """ + ... + + +def passkey_enter(passkey: int, /) -> None: + """Allows user entry of a passkey value. + + :param passkey: The numeric value of the passkey provided by + the user. + + The ``passkey_enter`` function is new in the following firmware versions: + * XBee3 Cellular LTE-M/NB-IoT: version 11416 + * XBee3 Cellular LTE Cat 1: version x16 + """ + ...