--- a/src/eric7/MicroPython/Devices/MicrobitDevices.py Thu Mar 09 11:13:35 2023 +0100 +++ b/src/eric7/MicroPython/Devices/MicrobitDevices.py Thu Mar 09 14:53:36 2023 +0100 @@ -54,6 +54,13 @@ self.__createMicrobitMenu() + self.__bleAddressType = { + 0: self.tr("Public"), + 1: self.tr("Random Static"), + 2: self.tr("Random Private Resolvable"), + 3: self.tr("Random Private Non-Resolvable"), + } + def setConnected(self, connected): """ Public method to set the connection state. @@ -718,6 +725,236 @@ else: return "" + ################################################################## + ## Methods below implement Bluetooth related methods + ## + ## Note: These functions are only available on BBC micro:bit v2 + ## with CircuitPython firmware loaded. This is handled + ## through the 'hasBluetooth()' method. + ## + ## The Bluetooth related code below is a copy of the one found in + ## the CircuitPythonDevices.py module with modifications to cope + ## with the limited set of available modules (e.g. no binascii + ## or json). + ################################################################## + + def hasBluetooth(self): + """ + Public method to check the availability of Bluetooth. + + @return flag indicating the availability of Bluetooth + @rtype bool + @exception OSError raised to indicate an issue with the device + """ + if not self.hasCircuitPython(): + return False + + command = """ +def has_bt(): + try: + import _bleio + if hasattr(_bleio, 'adapter'): + return True + except ImportError: + pass + + return False + +print(has_bt()) +del has_bt +""" + out, err = self._interface.execute( + command, mode=self._submitMode, timeout=10000 + ) + if err: + raise OSError(self._shortError(err)) + return out.strip() == b"True" + + def getBluetoothStatus(self): + """ + Public method to get Bluetooth status data of the connected board. + + @return list of tuples containing the translated status data label and + the associated value + @rtype list of tuples of (str, str) + @exception OSError raised to indicate an issue with the device + """ + command = """ +def ble_status(): + import _bleio + + def address2str(address): + return ':'.join('{0:02x}'.format(x) for x in address) + + a = _bleio.adapter + + ble_enabled = a.enabled + if not ble_enabled: + a.enabled = True + + res = { + 'active': ble_enabled, + 'mac': address2str(bytes(reversed(a.address.address_bytes))), + 'addr_type': a.address.type, + 'name': a.name, + 'advertising': a.advertising, + 'connected': a.connected, + } + + if not ble_enabled: + a.enabled = False + + print(res) + +ble_status() +del ble_status +""" + out, err = self._interface.execute(command, mode=self._submitMode) + if err: + raise OSError(self._shortError(err)) + + status = [] + bleStatus = ast.literal_eval(out.decode("utf-8")) + status.append((self.tr("Active"), self.bool2str(bleStatus["active"]))) + status.append((self.tr("Name"), bleStatus["name"])) + status.append((self.tr("MAC-Address"), bleStatus["mac"])) + status.append( + (self.tr("Address Type"), self.__bleAddressType[bleStatus["addr_type"]]) + ) + status.append((self.tr("Connected"), self.bool2str(bleStatus["connected"]))) + status.append((self.tr("Advertising"), self.bool2str(bleStatus["advertising"]))) + + return status + + def activateBluetoothInterface(self): + """ + Public method to activate the Bluetooth interface. + + @return flag indicating the new state of the Bluetooth interface + @rtype bool + @exception OSError raised to indicate an issue with the device + """ + command = """ +def activate_ble(): + import _bleio + + a = _bleio.adapter + if not a.enabled: + a.enabled = True + print(a.enabled) + +activate_ble() +del activate_ble +""" + out, err = self._interface.execute(command, mode=self._submitMode) + if err: + raise OSError(self._shortError(err)) + + return out.strip() == b"True" + + def deactivateBluetoothInterface(self): + """ + Public method to deactivate the Bluetooth interface. + + @return flag indicating the new state of the Bluetooth interface + @rtype bool + @exception OSError raised to indicate an issue with the device + """ + command = """ +def deactivate_ble(): + import _bleio + + a = _bleio.adapter + if a.enabled: + a.enabled = False + print(a.enabled) + +deactivate_ble() +del deactivate_ble +""" + out, err = self._interface.execute(command, mode=self._submitMode) + if err: + raise OSError(self._shortError(err)) + + return out.strip() == b"True" + + def getDeviceScan(self, timeout=10): + """ + Public method to perform a Bluetooth device scan. + + @param timeout duration of the device scan in seconds (defaults + to 10) + @type int (optional) + @return tuple containing a dictionary with the scan results and + an error string + @rtype tuple of (dict, str) + """ + from ..BluetoothDialogs.BluetoothAdvertisement import ( + ADV_IND, + ADV_SCAN_IND, + SCAN_RSP, + BluetoothAdvertisement, + ) + + command = """ +def ble_scan(): + import _bleio + import time + + def address2str(address): + return ':'.join('{{0:02x}}'.format(x) for x in address) + + a = _bleio.adapter + + ble_enabled = a.enabled + if not ble_enabled: + a.enabled = True + + scanResults = a.start_scan( + buffer_size=1024, extended=True, timeout={0}, minimum_rssi=-120, active=True + ) + time.sleep({0}) + a.stop_scan() + + for res in scanResults: + print({{ + 'address': address2str(bytes(reversed(res.address.address_bytes))), + 'advertisement': res.advertisement_bytes, + 'connectable': res.connectable, + 'rssi': res.rssi, + 'scan_response': res.scan_response, + }}) + + if not ble_enabled: + a.enabled = False + +ble_scan() +del ble_scan +""".format( + timeout + ) + out, err = self._interface.execute( + command, mode=self._submitMode, timeout=(timeout + 5) * 1000 + ) + if err: + return {}, err + + scanResults = {} + for line in out.decode("utf-8").splitlines(): + res = ast.literal_eval(line) + address = res["address"] + if address not in scanResults: + scanResults[address] = BluetoothAdvertisement(address) + if res["scan_response"]: + advType = SCAN_RSP + elif res["connectable"]: + advType = ADV_IND + else: + advType = ADV_SCAN_IND + scanResults[address].update(advType, res["rssi"], res["advertisement"]) + + return scanResults, "" + def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): """