class RileyLink(PacketRadio): def __init__(self): self.peripheral = None self.pa_level_index = PA_LEVELS.index(0x84) self.data_handle = None self.logger = getLogger() self.address = None if os.path.exists(RILEYLINK_MAC_FILE): with open(RILEYLINK_MAC_FILE, "r") as stream: self.address = stream.read() self.service = None self.response_handle = None self.notify_event = Event() self.initialized = False def connect(self, force_initialize=False): try: if self.address is None: self.address = self._findRileyLink() if self.peripheral is None: self.peripheral = Peripheral() try: state = self.peripheral.getState() if state == "conn": return except BTLEException: pass self._connect_retry(3) self.service = self.peripheral.getServiceByUUID(RILEYLINK_SERVICE_UUID) data_char = self.service.getCharacteristics(RILEYLINK_DATA_CHAR_UUID)[0] self.data_handle = data_char.getHandle() char_response = self.service.getCharacteristics(RILEYLINK_RESPONSE_CHAR_UUID)[0] self.response_handle = char_response.getHandle() response_notify_handle = self.response_handle + 1 notify_setup = b"\x01\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) while self.peripheral.waitForNotifications(0.05): self.peripheral.readCharacteristic(self.data_handle) if self.initialized: self.init_radio(force_initialize) else: self.init_radio(True) except BTLEException: if self.peripheral is not None: self.disconnect() raise def disconnect(self, ignore_errors=True): try: if self.peripheral is None: self.logger.info("Already disconnected") return self.logger.info("Disconnecting..") if self.response_handle is not None: response_notify_handle = self.response_handle + 1 notify_setup = b"\x00\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) except BTLEException: if not ignore_errors: raise finally: try: if self.peripheral is not None: self.peripheral.disconnect() self.peripheral = None except BTLEException: if ignore_errors: self.logger.exception("Ignoring btle exception during disconnect") else: raise def get_info(self): try: self.connect() bs = self.peripheral.getServiceByUUID(XGATT_BATTERYSERVICE_UUID) bc = bs.getCharacteristics(XGATT_BATTERY_CHAR_UUID)[0] bch = bc.getHandle() battery_value = int(self.peripheral.readCharacteristic(bch)[0]) self.logger.debug("Battery level read: %d", battery_value) version, v_major, v_minor = self._read_version() return { "battery_level": battery_value, "mac_address": self.address, "version_string": version, "version_major": v_major, "version_minor": v_minor } except BTLEException as btlee: raise PacketRadioError("Error communicating with RileyLink") from btlee finally: self.disconnect() def _read_version(self): version = None try: if os.path.exists(RILEYLINK_VERSION_FILE): with open(RILEYLINK_VERSION_FILE, "r") as stream: version = stream.read() else: response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) try: with open(RILEYLINK_VERSION_FILE, "w") as stream: stream.write(version) except IOError: self.logger.exception("Failed to store version in file") if version is None: return "0.0", 0, 0 try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError("Failed to parse firmware version string: %s" % version) v_major = int(m.group(1)) v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (v_major, v_minor)) return version, v_major, v_minor except Exception as ex: raise PacketRadioError("Failed to parse firmware version string: %s" % version) from ex except IOError: self.logger.exception("Error reading version file") except PacketRadioError: raise response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError("Failed to parse firmware version string: %s" % version) v_major = int(m.group(1)) v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (v_major, v_minor)) return (version, v_major, v_minor) except PacketRadioError: raise except Exception as ex: raise PacketRadioError("Failed to parse firmware version string: %s" % version) from ex def init_radio(self, force_init=False): try: version, v_major, v_minor = self._read_version() if v_major < 2: self.logger.error("Firmware version is below 2.0") raise PacketRadioError("Unsupported RileyLink firmware %d.%d (%s)" % (v_major, v_minor, version)) if not force_init: if v_major == 2 and v_minor < 3: response = self._command(Command.READ_REGISTER, bytes([Register.SYNC1, 0x00])) else: response = self._command(Command.READ_REGISTER, bytes([Register.SYNC1])) if response is not None and len(response) > 0 and response[0] == 0xA5: return self._command(Command.RADIO_RESET_CONFIG) self._command(Command.SET_SW_ENCODING, bytes([Encoding.MANCHESTER])) frequency = int(433910000 / (24000000 / pow(2, 16))) self._command(Command.SET_PREAMBLE, bytes([0x66, 0x65])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, frequency & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, (frequency >> 8) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, (frequency >> 16) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x20])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xCA])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xBC])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x70])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 35])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) response = self._command(Command.GET_STATE) if response != b"OK": raise PacketRadioError("Rileylink state is not OK. Response returned: %s" % response) self.initialized = True except PacketRadioError as rle: self.logger.error("Error while initializing rileylink radio: %s", rle) raise def tx_up(self): if self.pa_level_index < len(PA_LEVELS) - 1: self.pa_level_index += 1 self._set_amp(self.pa_level_index) def tx_down(self): if self.pa_level_index > 0: self.pa_level_index -= 1 self._set_amp(self.pa_level_index) def set_tx_power(self, tx_power): if tx_power is None: return elif tx_power == TxPower.Lowest: self._set_amp(0) elif tx_power == TxPower.Low: self._set_amp(PA_LEVELS.index(0x12)) elif tx_power == TxPower.Normal: self._set_amp(PA_LEVELS.index(0x60)) elif tx_power == TxPower.High: self._set_amp(PA_LEVELS.index(0xC8)) elif tx_power == TxPower.Highest: self._set_amp(PA_LEVELS.index(0xC0)) def get_packet(self, timeout=5.0): try: self.connect() return self._command(Command.GET_PACKET, struct.pack(">BL", 0, int(timeout * 1000)), timeout=float(timeout)+0.5) except PacketRadioError as rle: self.logger.error("Error while receiving data: %s", rle) raise def send_and_receive_packet(self, packet, repeat_count, delay_ms, timeout_ms, retry_count, preamble_ext_ms): try: self.connect() return self._command(Command.SEND_AND_LISTEN, struct.pack(">BBHBLBH", 0, repeat_count, delay_ms, 0, timeout_ms, retry_count, preamble_ext_ms) + packet, timeout=30) except PacketRadioError as rle: self.logger.error("Error while sending and receiving data: %s", rle) raise def send_packet(self, packet, repeat_count, delay_ms, preamble_extension_ms): try: self.connect() result = self._command(Command.SEND_PACKET, struct.pack(">BBHH", 0, repeat_count, delay_ms, preamble_extension_ms) + packet, timeout=30) return result except PacketRadioError as rle: self.logger.error("Error while sending data: %s", rle) raise def _set_amp(self, index=None): try: self.connect() if index is not None: self.pa_level_index = index self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self.logger.debug("Setting pa level to index %d of %d" % (self.pa_level_index, len(PA_LEVELS))) except PacketRadioError: self.logger.exception("Error while setting tx amplification") raise def _findRileyLink(self): scanner = Scanner() found = None self.logger.debug("Scanning for RileyLink") retries = 10 while found is None and retries > 0: retries -= 1 for result in scanner.scan(1.0): if result.getValueText(7) == RILEYLINK_SERVICE_UUID: self.logger.debug("Found RileyLink") found = result.addr try: with open(RILEYLINK_MAC_FILE, "w") as stream: stream.write(result.addr) except IOError: self.logger.warning("Cannot store rileylink mac radio_address for later") break if found is None: raise PacketRadioError("Could not find RileyLink") return found def _connect_retry(self, retries): while retries > 0: retries -= 1 self.logger.info("Connecting to RileyLink, retries left: %d" % retries) try: self.peripheral.connect(self.address) self.logger.info("Connected") break except BTLEException as btlee: self.logger.warning("BTLE exception trying to connect: %s" % btlee) try: p = subprocess.Popen(["ps", "-A"], stdout=subprocess.PIPE) out, err = p.communicate() for line in out.splitlines(): if "bluepy-helper" in line: pid = int(line.split(None, 1)[0]) os.kill(pid, 9) break except: self.logger.warning("Failed to kill bluepy-helper") time.sleep(1) def _command(self, command_type, command_data=None, timeout=10.0): try: if command_data is None: data = bytes([1, command_type]) else: data = bytes([len(command_data) + 1, command_type]) + command_data self.peripheral.writeCharacteristic(self.data_handle, data, withResponse=True) if not self.peripheral.waitForNotifications(timeout): raise PacketRadioError("Timed out while waiting for a response from RileyLink") response = self.peripheral.readCharacteristic(self.data_handle) if response is None or len(response) == 0: raise PacketRadioError("RileyLink returned no response") else: if response[0] == Response.COMMAND_SUCCESS: return response[1:] elif response[0] == Response.COMMAND_INTERRUPTED: self.logger.warning("A previous command was interrupted") return response[1:] elif response[0] == Response.RX_TIMEOUT: return None else: raise PacketRadioError("RileyLink returned error code: %02X. Additional response data: %s" % (response[0], response[1:]), response[0]) except Exception as e: raise PacketRadioError("Error executing command") from e
def get_beacon_key(mac, product_id): reversed_mac = reverseMac(mac) token = generateRandomToken() # Pairing input(f"Activate pairing on your '{mac}' device, then press Enter: ") # Connect print("Connection in progress...") peripheral = Peripheral(deviceAddr=mac) print("Successful connection!") # Auth (More information: https://github.com/archaron/docs/blob/master/BLE/ylkg08y.md) print("Authentication in progress...") auth_service = peripheral.getServiceByUUID(UUID_SERVICE) auth_descriptors = auth_service.getDescriptors() peripheral.writeCharacteristic(HANDLE_AUTH_INIT, MI_KEY1, "true") auth_descriptors[1].write(SUBSCRIBE_TRUE, "true") peripheral.writeCharacteristic( HANDLE_AUTH, cipher(mixA(reversed_mac, product_id), token), "true") peripheral.waitForNotifications(10.0) peripheral.writeCharacteristic(3, cipher(token, MI_KEY2), "true") print("Successful authentication!") # Read beacon_key = cipher( token, peripheral.readCharacteristic(HANDLE_BEACON_KEY)).hex() firmware_version = cipher( token, peripheral.readCharacteristic(HANDLE_FIRMWARE_VERSION)).decode() print(f"beaconKey: '{beacon_key}'") print(f"firmware_version: '{firmware_version}'")
def __get_raw_data(self, force_update=False): if self.__address is None: return starttime = int(time.time()) if force_update or starttime - self.__cached_data[ 'last_update'] > terrariumMiFloraSensor.__CACHE_TIMEOUT: try: cached_data = {} for item in self.__cached_data: cached_data[item] = None miflora_dev = Peripheral(self.__address) #Read battery and firmware version attribute cached_data['battery'], cached_data['firmware'] = unpack( '<xB5s', miflora_dev.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_FIRMWARE_AND_BATTERY)) #Enable real-time data reading miflora_dev.writeCharacteristic( terrariumMiFloraSensor.__MIFLORA_REALTIME_DATA_TRIGGER, bytearray([0xa0, 0x1f]), True) #Read plant data cached_data['temperature'], cached_data['light'], cached_data[ 'moisture'], cached_data['fertility'] = unpack( '<hxIBHxxxxxx', miflora_dev.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_GET_DATA)) # Close connection... miflora_dev.disconnect() cached_data['last_update'] = starttime for item in self.__cached_data: self.__cached_data[item] = cached_data[item] self.__errors = 0 except Exception as ex: self.__errors += 1 if self.__errors > 3: logger.error( 'Error getting new data from sensor at address: \'%s\'. Error: %s' % (self.__address, ex)) else: logger.warning( 'Error getting new data from sensor at address: \'%s\'. Error: %s' % (self.__address, ex))
def readData (self, mqttClient): print("reading data from device " + self.__mac) sys.stdout.flush() p = Peripheral(self.__mac, iface=HCI_INTERFACE) p.withDelegate(self.__delegate) try: battery = p.readCharacteristic(BATTERY_HANDLE) self.__lastBattery = battery[0] except: print("failed to read battery from " + self.__mac) sys.stdout.flush() p.writeCharacteristic(TEMP_HUM_WRITE_HANDLE, TEMP_HUM_WRITE_VALUE) if not p.waitForNotifications(3.0): print("failed to read data from " + self.__mac) sys.stdout.flush() try: p.disconnect() except: pass print("read data from " + self.__mac + " " + str(self.__lastTemp) + "," + str(self.__lastHum) + "," + str(self.__lastBattery)) sys.stdout.flush() msg =\ '{'\ '"idx" : ' + str(self.__id) + ','\ '"nvalue" : 0,'\ '"svalue" : "' + str(self.__lastTemp) + ';' + str(self.__lastHum) + ';0",'\ '"Battery" : ' + str(self.__lastBattery) + ' '\ '}' mqttClient.publish(DEFAULT_IN_TOPIC, msg);
def read_value_for_charateristics(self, characteristics: typing.List[Characteristic], service: Service, peripheral: Peripheral, device: ScanEntry): characteristics_info = list() for c in characteristics: try: # Check if Extended properties DBG("Characteristic {} Properties {}".format(c, c.propertiesToString()), logLevel=LogLevel.DEBUG) if c.properties & Characteristic.props["EXTENDED"]: DBG("Uses extended properties", logLevel=LogLevel.DEBUG) characteristics_info.append((c, b'')) continue if c.supportsRead() and not c.uuid in self.failing_characteristics: value = peripheral.readCharacteristic(c.valHandle, timeout=self.ble_timeout) characteristics_info.append((c, value)) DBG("Read {} and received {}".format(c.uuid.getCommonName(), value)) else: characteristics_info.append((c, b'')) except Exception as e: DBG("Could not read characteristic {} - {}".format(c.uuid.getCommonName(), c.uuid.binVal), logLevel=LogLevel.DEBUG) # DBG(e.with_traceback(), logLevel=LogLevel.ERR) characteristics_info.append((c, b'')) # peripheral = self.reconnect_to_device(device, old_peripheral=peripheral) # if not peripheral: # break self.relay_service_information(device, service, characteristics_info)
class Light: def __init__(self, name, addr): self.device = Peripheral(deviceAddr=addr) self.name = name self.address = addr def to_json(self): return "{" + f'"name": "{self.name}", "address": "{self.address}"' + "}" def to_object(self): return {'name': self.name, 'address': self.address} def setLight(self, blue, white): self.device.writeCharacteristic(SERVICE_HANDLE, getBrightnessCommand(blue, white)) def getLight(self): c = bytes.hex(self.device.readCharacteristic(SERVICE_HANDLE)) return {'blue': int(c[8:10], 16), 'white': int(c[10:12], 16)} def turn_on(self): self.device.writeCharacteristic(SERVICE_HANDLE, bytes.fromhex("FBF0 FA")) def turn_off(self): self.device.writeCharacteristic(SERVICE_HANDLE, bytes.fromhex("FB0F FA"))
class BluepyBackend(AbstractBackend): def __init__(self, adapter='hci0'): super(self.__class__, self).__init__(adapter) self._peripheral = None def connect(self, mac): from bluepy.btle import Peripheral self._peripheral = Peripheral(mac) def disconnect(self): self._peripheral.disconnect() self._peripheral = None def read_handle(self, handle): if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.readCharacteristic(handle) def write_handle(self, handle, value): if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.writeCharacteristic(handle, value, True) def check_backend(self): try: import bluepy.btle # noqa: F401 except ImportError: raise BluetoothBackendException('bluepy not found')
def handleDiscovery(self, dev, isNewDev, isNewData): if self.mac_address != None and self.mac_address != dev.addr: return if dev.addr in self.seen: return self.seen.append(dev.addr) AD_TYPE_SERVICE_DATA = 0x16 service = dev.getValueText(SERVICES_AD_TYPE) val = None if service == OLD_FIRMWARE_SERVICE: data = dev.getValueText(AD_TYPE_SERVICE_DATA) # the bit at 0x0100 signifies if the switch is off or on val = (int(data, 16) >> 8) & 1 elif service == NEW_FIRMWARE_SERVICE: try: peripheral = Peripheral(dev.addr, ADDR_TYPE_RANDOM) val = ord(peripheral.readCharacteristic(NEW_STATE_HANDLE)[0]) except Exception as ex: print('WARNING: Could not read status of {}. {}'.format(dev.addr, ex.message)) if val is not None: print(dev.addr + ' ' + ("off", "on")[val]) if self.mac_address != None: sys.exit()
class BluepyBackend(AbstractBackend): """Backend for Miflora using the bluepy library.""" def __init__(self, adapter='hci0'): """Create new instance of the backend.""" super(self.__class__, self).__init__(adapter) self._peripheral = None def connect(self, mac): """Connect to a device.""" from bluepy.btle import Peripheral m = re.search(r'hci([\d]+)', self.adapter) if m is None: raise ValueError('Invalid pattern "{}" for BLuetooth adpater. ' 'Expetected something like "hci0".'.format( self.adapter)) iface = int(m.group(1)) self._peripheral = Peripheral(mac, iface=iface) def disconnect(self): """Disconnect from a device.""" self._peripheral.disconnect() self._peripheral = None def read_handle(self, handle): """Read a handle from the device. You must be connected to do this. """ if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.readCharacteristic(handle) def write_handle(self, handle, value): """Write a handle from the device. You must be connected to do this. """ if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.writeCharacteristic(handle, value, True) def check_backend(self): """Check if the backend is available.""" try: import bluepy.btle # noqa: F401 except ImportError: raise BluetoothBackendException('bluepy not found') @staticmethod def scan_for_devices(timeout): """Scan for bluetooth low energy devices. Note this must be run as root!""" from bluepy.btle import Scanner scanner = Scanner() result = [] for device in scanner.scan(timeout): result.append((device.addr, device.getValueText(9))) return result
def write_state(self, value): from bluepy.btle import Peripheral device = None try: # connect to device device = Peripheral(self._mac) # send PIN code auth device.writeCharacteristic( 0x3c, self._pin.to_bytes(4, byteorder='little'), True) _LOGGER.info("Auth success for {}".format(self._mac)) # set date+time now = datetime.now() device.writeCharacteristic( 0x25, bytes([ now.minute, now.hour, now.day, now.month, now.year - 2000 ])) # handle any outstanding value updates if value: device.writeCharacteristic(0x35, value, True) # writing something here does a "commit" device.writeCharacteristic(0x3a, bytes([randint(0, 255)])) _LOGGER.info("Updated switch to {}".format(value[0])) self._state = (device.readCharacteristic(0x35)[0] & 8 > 0) except Exception as e: _LOGGER.error("Exception: %s ", str(e)) finally: if device is not None: device.disconnect()
def read_characteristics(device, characteristics): try: p = Peripheral(device.address, device.addressType) for c in characteristics: c.data = p.readCharacteristic(c.handle) except BTLEException: print("could not connect to", device.address) pass return characteristics
class PiBuddy(): def __init__(self, address): self.device = Peripheral(address) self.read = self.device.getCharacteristics( uuid="6E400003-B5A3-F393-E0A9-E50E24DCCA9E")[0] self.readHandle = self.read.getHandle() def getCurrentStatus(self): return self.device.readCharacteristic(self.readHandle)
def readnewFirmware(macaddress): try: peripheral = Peripheral(macaddress, ADDR_TYPE_RANDOM) val = ord(peripheral.readCharacteristic(NEW_STATE_HANDLE)) data = (convertMac(macaddress), (False, True)[val], datetime.datetime.now()) insertDBRecord(data) peripheral.disconnect() except Exception as ex: print('WARNING: Could not read status of {}. {}'.format( macaddress, ex.message))
def load_data(self): data = None if self.get_address() is not None: try: sensor = Peripheral(self.get_address()) #Read battery and firmware version attribute data = unpack( '<xB5s', sensor.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_FIRMWARE_AND_BATTERY)) data = {'battery': data[0], 'firmware': data[1]} #Enable real-time data reading sensor.writeCharacteristic( terrariumMiFloraSensor.__MIFLORA_REALTIME_DATA_TRIGGER, bytearray([0xa0, 0x1f]), True) #Read plant data data['temperature'], data['light'], data['moisture'], data[ 'fertility'] = unpack( '<hxIBHxxxxxx', sensor.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_GET_DATA)) # Close connection... sensor.disconnect() # Clean up data['temperature'] = float(data['temperature']) / 10.0 data['light'] = float(data['light']) data['moisture'] = float(data['moisture']) data['fertility'] = float(data['fertility']) data['battery'] = float(data['battery']) data['firmware'] = data['firmware'].decode('utf8') except Exception as ex: logger.warning( 'Error getting new data from {} sensor \'{}\'. Error message: {}' .format(self.get_type(), self.get_name(), ex)) return data
def __check_connection(self): if self.__address is None: return False try: miflora_dev = Peripheral(self.__address) #Read battery and firmware version attribute self.__cached_data['battery'], self.__cached_data['firmware'] = unpack('<xB5s',miflora_dev.readCharacteristic(terrariumMiFloraSensor.__MIFLORA_FIRMWARE_AND_BATTERY)) miflora_dev.disconnect() return True except Exception, ex: print ex
def read_services(device): if not isinstance(device, LEDevice): return device try: p = Peripheral(device.address, device.addressType) for s in device.services: for c in s.characteristics: c.data = p.readCharacteristic(c.handle) except BTLEException: print("could not connect to", device.address) pass return device
def read(self, retry): (temperature, humidity) = (None, None) try: peripheral = Peripheral(self.device, addrType=ADDR_TYPE_PUBLIC) characteristic = peripheral.readCharacteristic(self.handle) temperature = self.read_temperature(characteristic) humidity = self.read_humidity(characteristic) except BTLEDisconnectError as e: logger.info(e) if retry < RETRY: (temperature, humidity) = self.read(retry+1) else: logger.info("read retry error") return (temperature, humidity)
class Switchmate(SwitchDevice): """Representation of a Switchmate.""" def __init__(self, mac, friendly_name) -> None: """Initialize the Switchmate.""" from bluepy.btle import ADDR_TYPE_RANDOM, Peripheral, BTLEException self._state = False self._friendly_name = friendly_name self._handle = 0x2e self._mac = mac try: self._device = Peripheral(self._mac, ADDR_TYPE_RANDOM) except BTLEException as ex: _LOGGER.error("Failed to setup switchmate: " + ex.message) raise PlatformNotReady() @property def unique_id(self) -> str: """Return a unique, HASS-friendly identifier for this entity.""" return '{0}_{1}'.format(self._mac.replace(':', ''), self.entity_id) @property def name(self) -> str: """Return the name of the switch.""" return self._friendly_name @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self) -> None: """Synchronize state with switch.""" self._state = b'\x00' == self._device.readCharacteristic(self._handle) print("state", self._state) @property def is_on(self) -> bool: """Return true if it is on.""" return self._state def turn_on(self, **kwargs) -> None: """Turn the switch on.""" self._device.writeCharacteristic(self._handle, b'\x00', True) self._state = True self.schedule_update_ha_state() def turn_off(self, **kwargs) -> None: """Turn the switch off.""" self._device.writeCharacteristic(self._handle, b'\x01', True) self._state = False self.schedule_update_ha_state()
def check_connection(address): tmp = {} try: miflora_dev = Peripheral(address) #Read battery and firmware version attribute tmp['battery'], tmp['firmware'] = unpack( '<xB5s', miflora_dev.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_FIRMWARE_AND_BATTERY)) miflora_dev.disconnect() return True except Exception as ex: logger.error( 'Error checking online state sensor at address: \'{}\'. Error: {}' .format(address, ex)) return False
def __check_connection(self): if self.__address is None: return False try: miflora_dev = Peripheral(self.__address) #Read battery and firmware version attribute self.__cached_data['battery'], self.__cached_data[ 'firmware'] = unpack( '<xB5s', miflora_dev.readCharacteristic( terrariumMiFloraSensor.__MIFLORA_FIRMWARE_AND_BATTERY)) miflora_dev.disconnect() return True except Exception as ex: logger.error( 'Error checking online state sensor at address: \'%s\'. Error: %s' % (self.__address, ex)) return False
class Switch(object): """ A switch class for switchmate switches """ def __init__(self, mac_address='c1:59:2c:b2:8d:33'): """ return a switch object with the right mac_address""" self.mac_address = mac_address try: self.device = Peripheral(mac_address, ADDR_TYPE_RANDOM) self.state_handle = get_state_handle(self.device) self.curr_val = self.device.readCharacteristic(self.state_handle) except BTLEException as ex: print('ERROR: ' + ex.message) except OSError as ex: print('ERROR: Failed to connect to device.') @property def status(self, ): return 'off' if self.curr_val == b'\x00' else 'on' def switch(self, state=None): """ Switch the switchmate on or off Usage: switch('on') """ if state == 'on': val = b'\x01' elif state == 'off': val = b'\x00' elif state == None: val = b'\x01' if self.curr_val == b'\x00' else b'\x00' try: self.device.writeCharacteristic(self.state_handle, val, True) self.curr_val = val except BTLEException as ex: print_exception(ex) # Functions for a cleaner interface def turn_on(self): self.switch('on') def turn_off(self): self.switch('off')
def readData(self): print("reading data from device " + self.__mac) p = Peripheral(self.__mac, iface=0) p.withDelegate(self.__delegate) try: battery = p.readCharacteristic(BATTERY_HANDLE) self.__lastBattery = battery[0] except: print("failed to read battery from " + self.__mac) p.writeCharacteristic(TEMP_HUM_WRITE_HANDLE, TEMP_HUM_WRITE_VALUE) if not p.waitForNotifications(3.0): print("failed to read data from " + self.__mac) print('Temperature is ' + str(self.__lastTemp)) print('Humidity is ' + str(self.__lastHum)) print('Battery is ' + str(self.__lastBattery)) try: p.disconnect() except: pass
def main(): bluetooth_adr = sys.argv[1].lower() port = int(sys.argv[2]) host = get_network_interface_ip_address(sys.argv[3]) #BT HCI 0 or 1 iface = int(sys.argv[4]) print "Will follow broadcasts from: ", bluetooth_adr, ". SmartThings DeviceHandler will have to be configured with IP:", host, " and port: ", port print "hci used: ", iface mac1, mac2, mac3, mac4, mac5, mac6 = bluetooth_adr.split(':') status = HFPStatus() perif = Peripheral() perif.setDelegate(HFPProDelegate(bluetooth_adr, 'init', status)) #wait for commands s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a socket object s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.settimeout(5.0) print "Connecting", while True: try: perif.connect(bluetooth_adr, iface=iface) perif.writeCharacteristic( 0x25, "\x4d\x41\x43\x2b" + binascii.unhexlify(mac1) + binascii.unhexlify(mac2) + binascii.unhexlify(mac3) + binascii.unhexlify(mac4) + binascii.unhexlify(mac5) + binascii.unhexlify(mac6)) perif.readCharacteristic(0x2e) perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) break except BTLEException: print ".", print "" print "Connected to ", bluetooth_adr try: s.bind((host, port)) # Bind to the port except: while True: sleep(10) try: s.bind((host, port)) break except: continue if s != None: while True: try: # reactivate BT link by writing the Notification Characatestic every 5 sec perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) conn = None command = None s.listen(5) try: conn, addr = s.accept( ) # Establish connection with client. #print 'Got connection from', addr data = conn.recv(1024) print('Server received', repr(data)) #extract the command from 'GET /api/commmand/value command = re.findall('GET /api/(\S*)/', repr(data)) if command != None: print command[0] # Parse value value = re.findall('GET /api/' + command[0] + '/(\S*)', repr(data)) if value != None: #Commands if command[0] == 'fanspeed': # Fan Speed # /api/fanspeed/4 turbo # /api/fanspeed/3 allergen # /api/fanspeed/2 general_on # /api/fanspeed/1 germ # /api/fanspeed/0 off if status.getFanSpeed() != value[0]: if value[0] == 'turbo': #'turbo': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a510009316000000000000000000000000000000' )) elif value[0] == 'allergen': #'allergen: sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a508009316000000000000000000000000000000' )) elif value[0] == 'general_on': #'general': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a504009316000000000000000000000000000000' )) elif value[0] == 'germ': #'germ': if status.getFanSpeed() == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a501009316000000000000000000000000000000' )) else: sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a502009316000000000000000000000000000000' )) elif value[0] == 'off': #'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a501009316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'light': # /api/ligth/on # /api/light/medium # /api/light/off if value[0] == 'on': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) elif value[0] == 'medium': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) elif value[0] == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'timer': # /api/timer/plus # /api/timer/minus if value[0] == 'plus': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500019316000000000000000000000000000000' )) elif value[0] == 'minus': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500029316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'voc': # /api/voc/on # /api/voc/off if value[0] == 'on': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a520009316000000000000000000000000000000' )) elif value[0] == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a520009316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'refresh': # do nothing and just resent the last status. Maybe notification(s) were received in-between to status got update despite the Smartthings side didn't get refreshed yet. print "Refresh will be sent" else: print "Unkown command" else: print "Malformed value" #else: # print "Malformed command" if conn != None: conn.send( 'HTTP/1.1 200 OK\nContent-Type: application/json\n\n' + status.status()) conn.close() except socket.timeout: #not received anything. perif.waitForNotifications(1.0) pass except BTLEException as e: print 'Error on line {}'.format(sys.exc_info()[-1].tb_lineno) print e while True: try: print "Trying to reach again ", bluetooth_adr perif.connect(bluetooth_adr, iface=iface) perif.writeCharacteristic( 0x25, "\x4d\x41\x43\x2b" + binascii.unhexlify(mac1) + binascii.unhexlify(mac2) + binascii.unhexlify(mac3) + binascii.unhexlify(mac4) + binascii.unhexlify(mac5) + binascii.unhexlify(mac6)) perif.readCharacteristic(0x2e) perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) print "Re-connected BT-LE target" break except: pass
class MiKettle(object): """" A class to control mi kettle device. """ def __init__(self, mac, product_id, cache_timeout=600, retries=3, iface=None, token=None): """ Initialize a Mi Kettle for the given MAC address. """ _LOGGER.debug('Init Mikettle with mac %s and pid %s', mac, product_id) self._mac = mac self._reversed_mac = MiKettle.reverseMac(mac) self._cache = None self._cache_timeout = timedelta(seconds=cache_timeout) self._last_read = None self.retries = retries self.ble_timeout = 10 self.lock = Lock() self._product_id = product_id self._iface = iface # Generate token if not supplied if token is None: token = MiKettle.generateRandomToken() self._token = token self._p = None self._authenticated = False def connect(self): if self._p is None: self._p = Peripheral(deviceAddr=self._mac, iface=self._iface) self._p.setDelegate(self) def name(self): """Return the name of the device.""" self.connect() self.auth() name = self._p.readCharacteristic(_HANDLE_READ_NAME) if not name: raise Exception("Could not read NAME using handle %s" " from Mi Kettle %s" % (_HANDLE_READ_NAME, self._mac)) return ''.join(chr(n) for n in name) def firmware_version(self): """Return the firmware version.""" self.connect() self.auth() firmware_version = self._p.readCharacteristic(_HANDLE_READ_FIRMWARE_VERSION) if not firmware_version: raise Exception("Could not read FIRMWARE_VERSION using handle %s" " from Mi Kettle %s" % (_HANDLE_READ_FIRMWARE_VERSION, self._mac)) return ''.join(chr(n) for n in firmware_version) def parameter_value(self, parameter, read_cached=True): """Return a value of one of the monitored paramaters. This method will try to retrieve the data from cache and only request it by bluetooth if no cached value is stored or the cache is expired. This behaviour can be overwritten by the "read_cached" parameter. """ # Use the lock to make sure the cache isn't updated multiple times with self.lock: if (read_cached is False) or \ (self._last_read is None) or \ (datetime.now() - self._cache_timeout > self._last_read): self.fill_cache() else: _LOGGER.debug("Using cache (%s < %s)", datetime.now() - self._last_read, self._cache_timeout) if self.cache_available(): return self._cache[parameter] else: raise Exception("Could not read data from MiKettle %s" % self._mac) def fill_cache(self): """Fill the cache with new data from the sensor.""" _LOGGER.debug('Filling cache with new sensor data.') try: _LOGGER.debug('Connect') self.connect() _LOGGER.debug('Auth') self.auth() _LOGGER.debug('Subscribe') self.subscribeToData() _LOGGER.debug('Wait for data') self._p.waitForNotifications(self.ble_timeout) # If a sensor doesn't work, wait 5 minutes before retrying except Exception as error: _LOGGER.debug('Error %s', error) self._last_read = datetime.now() - self._cache_timeout + \ timedelta(seconds=300) return def clear_cache(self): """Manually force the cache to be cleared.""" self._cache = None self._last_read = None def cache_available(self): """Check if there is data in the cache.""" return self._cache is not None def _parse_data(self, data): """Parses the byte array returned by the sensor.""" _LOGGER.debug('parsing') res = dict() res[MI_ACTION] = MI_ACTION_MAP[int(data[0])] res[MI_MODE] = MI_MODE_MAP[int(data[1])] res[MI_SET_TEMPERATURE] = int(data[4]) _LOGGER.debug('Mode: %s', int(data[1])) res[MI_CURRENT_TEMPERATURE] = int(data[5]) res[MI_KW_TYPE] = MI_KW_TYPE_MAP[int(data[6])] res[MI_KW_TIME] = MiKettle.bytes_to_int(data[7:8]) return res @staticmethod def bytes_to_int(bytes): result = 0 for b in bytes: result = result * 256 + int(b) return result def auth(self): if self._authenticated: return auth_service = self._p.getServiceByUUID(_UUID_SERVICE_KETTLE) auth_descriptors = auth_service.getDescriptors() self._p.writeCharacteristic(_HANDLE_AUTH_INIT, _KEY1, "true") auth_descriptors[1].write(_SUBSCRIBE_TRUE, "true") self._p.writeCharacteristic(_HANDLE_AUTH, MiKettle.cipher(MiKettle.mixA(self._reversed_mac, self._product_id), self._token), "true") self._p.waitForNotifications(10.0) self._p.writeCharacteristic(_HANDLE_AUTH, MiKettle.cipher(self._token, _KEY2), "true") self._p.readCharacteristic(_HANDLE_VERSION) self._authenticated = True def subscribeToData(self): controlService = self._p.getServiceByUUID(_UUID_SERVICE_KETTLE_DATA) controlDescriptors = controlService.getDescriptors() controlDescriptors[3].write(_SUBSCRIBE_TRUE, "true") # TODO: Actually generate random token instead of static one @staticmethod def generateRandomToken() -> bytes: return bytes([0x01, 0x5C, 0xCB, 0xA8, 0x80, 0x0A, 0xBD, 0xC1, 0x2E, 0xB8, 0xED, 0x82]) @staticmethod def reverseMac(mac) -> bytes: parts = mac.split(":") reversedMac = bytearray() leng = len(parts) for i in range(1, leng + 1): reversedMac.extend(bytearray.fromhex(parts[leng - i])) return reversedMac @staticmethod def mixA(mac, productID) -> bytes: return bytes([mac[0], mac[2], mac[5], (productID & 0xff), (productID & 0xff), mac[4], mac[5], mac[1]]) @staticmethod def mixB(mac, productID) -> bytes: return bytes([mac[0], mac[2], mac[5], ((productID >> 8) & 0xff), mac[4], mac[0], mac[5], (productID & 0xff)]) @staticmethod def _cipherInit(key) -> bytes: perm = bytearray() for i in range(0, 256): perm.extend(bytes([i & 0xff])) keyLen = len(key) j = 0 for i in range(0, 256): j += perm[i] + key[i % keyLen] j = j & 0xff perm[i], perm[j] = perm[j], perm[i] return perm @staticmethod def _cipherCrypt(input, perm) -> bytes: index1 = 0 index2 = 0 output = bytearray() for i in range(0, len(input)): index1 = index1 + 1 index1 = index1 & 0xff index2 += perm[index1] index2 = index2 & 0xff perm[index1], perm[index2] = perm[index2], perm[index1] idx = perm[index1] + perm[index2] idx = idx & 0xff outputByte = input[i] ^ perm[idx] output.extend(bytes([outputByte & 0xff])) return output @staticmethod def cipher(key, input) -> bytes: perm = MiKettle._cipherInit(key) return MiKettle._cipherCrypt(input, perm) def handleNotification(self, cHandle, data): if cHandle == _HANDLE_AUTH: if(MiKettle.cipher(MiKettle.mixB(self._reversed_mac, self._product_id), MiKettle.cipher(MiKettle.mixA(self._reversed_mac, self._product_id), data)) != self._token): raise Exception("Authentication failed.") elif cHandle == _HANDLE_STATUS: _LOGGER.debug("Status update:") if data is None: return _LOGGER.debug("Parse data: %s", data) self._cache = self._parse_data(data) _LOGGER.debug("data parsed %s", self._cache) if self.cache_available(): self._last_read = datetime.now() else: # If a sensor doesn't work, wait 5 minutes before retrying self._last_read = datetime.now() - self._cache_timeout + \ timedelta(seconds=300) else: _LOGGER.error("Unknown notification from handle: %s with Data: %s", cHandle, data.hex())
# noinspection PyUnresolvedReferences from bluepy.btle import Scanner, DefaultDelegate, Peripheral # perf = Peripheral("00:07:80:BD:23:BE") perf = Peripheral("00:07:80:BD:1C:3A") print("Firmware: ", perf.readCharacteristic(0x000a)) print("Hardware: ", perf.readCharacteristic(0x000c)) val = perf.writeCharacteristic(0x001a, chr(20), withResponse=False) print("test: ", perf.readCharacteristic(0x001a), val) # perf.writeCharacteristic(0x001a,chr(1) + chr(0) + chr(1) + chr(100) + chr(1) + chr(0) + # chr(200)+ chr(2) + chr(0) + chr(200)+ chr(3) + chr(0) + chr(200)) # perf.writeCharacteristic(0x001a,chr(0) + chr(0) + chr(1) + chr(2) + chr(3))
try: p = Peripheral(args.BLEaddress, addrType="random") except BTLEException as ex: if args.verbose: print("Read failed. ", ex) time.sleep(10) try: p = Peripheral(args.BLEaddress, addrType="random") except: if args.verbose: print("Read failed. ", ex) exit else: result = p.readCharacteristic(0x15) if args.verbose: print(result) # Unpack into variables, skipping bytes 0-2 i = 3 PctCharged, V1Volts, V2Volts, Current, Power, Temperature, PowerMeter, ChargeMeter, TimeSinceStart, CurrentTime, PeakCurrent = struct.unpack_from( '<BfffffqqIIf', result, i) if args.verbose: print(PctCharged, V1Volts, V2Volts, Current, Power, Temperature, PowerMeter, ChargeMeter, TimeSinceStart, CurrentTime, PeakCurrent) # Clean up vars V1Volts = round(V1Volts, 2)
class SBrickCommunications(threading.Thread, IdleObject): def __init__(self, sbrick_addr): threading.Thread.__init__(self) IdleObject.__init__(self) self.lock = threading.RLock() self.drivingLock = threading.RLock() self.eventSend = threading.Event() self.sBrickAddr = sbrick_addr self.owner_password = None self.brickChannels = [ SBrickChannelDrive(0, self.eventSend), SBrickChannelDrive(1, self.eventSend), SBrickChannelDrive(2, self.eventSend), SBrickChannelDrive(3, self.eventSend), ] self.SBrickPeripheral = None self.stopFlag = False self.characteristicRemote = None self.need_authentication = False self.authenticated = False self.channel_config_ids = dict() def set_channel_config_id(self, channel, config_id): self.channel_config_ids[config_id] = channel self.brickChannels[channel].set_config_id(config_id) def terminate(self): self.stopFlag = True def is_driving(self): locked = self.drivingLock.acquire(False) if locked: self.drivingLock.release() return not locked def connect_to_sbrick(self, owner_password): self.owner_password = owner_password self.start() def run(self): try: monotime = 0.0 self.SBrickPeripheral = Peripheral() self.SBrickPeripheral.connect(self.sBrickAddr) service = self.SBrickPeripheral.getServiceByUUID('4dc591b0-857c-41de-b5f1-15abda665b0c') characteristics = service.getCharacteristics('02b8cbcc-0e25-4bda-8790-a15f53e6010f') for characteristic in characteristics: if characteristic.uuid == '02b8cbcc-0e25-4bda-8790-a15f53e6010f': self.characteristicRemote = characteristic if self.characteristicRemote is None: return self.emit('sbrick_connected') self.need_authentication = self.get_need_authentication() self.authenticated = not self.need_authentication if self.need_authentication: if self.password_owner is not None: self.authenticate_owner(self.password_owner) while not self.stopFlag: if self.authenticated: if monotonic.monotonic() - monotime >= 0.05: self.send_command() monotime = monotonic.monotonic() self.eventSend.wait(0.01) for channel in self.brickChannels: if channel.decrement_run_timer(): monotime = 0.0 self.drivingLock.release() # print("stop run normal") self.emit("sbrick_channel_stop", channel.channel) if channel.decrement_brake_timer(): self.drivingLock.release() # print("stop brake timer") monotime = 0.0 self.emit("sbrick_channel_stop", channel.channel) if self.authenticated: self.stop_all() self.send_command() self.SBrickPeripheral.disconnect() self.emit('sbrick_disconnected_ok') except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_channel(self, channel): if isinstance(channel, six.integer_types): return self.brickChannels[channel] if isinstance(channel, six.string_types): return self.brickChannels[self.channel_config_ids[channel]] return None def drive(self, channel, pwm, reverse, time, brake_after_time=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.drive(pwm, reverse, time, brake_after_time) self.emit("sbrick_drive_sent", ch.channel, time) self.eventSend.set() def stop(self, channel, braked=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.stop(braked) self.emit("sbrick_drive_sent", ch.channel, -2) self.eventSend.set() def stop_all(self): with self.lock: for channel in self.brickChannels: channel.stop() self.eventSend.set() def change_pwm(self, channel, pwm, change_reverse=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.set_pwm(pwm, change_reverse) self.eventSend.set() def change_reverse(self, channel, reverse): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.set_reverse(reverse) self.eventSend.set() def send_command(self): with self.lock: # try: drivecmd = bytearray([0x01]) brakecmd = bytearray([0x00]) for channel in self.brickChannels: drivecmd = channel.get_command_drive(drivecmd) brakecmd = channel.get_command_brake(brakecmd) if len(drivecmd) > 1: self.drivingLock.acquire() self.characteristicRemote.write(drivecmd, True) self.print_hex_string("drive sent", drivecmd) if len(brakecmd) > 1: self.characteristicRemote.write(brakecmd, True) self.print_hex_string("brake sent", brakecmd) # return True # except Exception as ex: # self.emit("sbrick_disconnected_error",ex.message) # return False def disconnect_sbrick(self): with self.lock: self.stopFlag = True @staticmethod def print_hex_string(what, strin): out = what + " -> " for chrx in strin: out = "%s %0X" % (out, chrx) print(out) def get_voltage(self): with self.lock: try: self.characteristicRemote.write(b"\x0f\x00") value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return (valueint * 0.83875) / 2047.0 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_temperature(self): with self.lock: try: self.characteristicRemote.write(b"\x0f\x0e") value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return valueint / 118.85795 - 160 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_thermal_limit(self): with self.lock: try: self.characteristicRemote.write(b'\x15') value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return valueint / 118.85795 - 160 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_watchdog_timeout(self): with self.lock: try: self.characteristicRemote.write(b'\x0e') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] * 0.1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_authentication_timeout(self): with self.lock: try: self.characteristicRemote.write(b'\x09') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] * 0.1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_power_cycle_counter(self): with self.lock: try: self.characteristicRemote.write(b'\x28') value = self.characteristicRemote.read() return struct.unpack("<I", value)[0] except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_uptime(self): with self.lock: try: self.characteristicRemote.write(b'\x29') value = self.characteristicRemote.read() seconds = struct.unpack("<I", value)[0] * 0.1 minutes = seconds // 60 hours = minutes // 60 return "%02d:%02d:%02d" % (hours, minutes % 60, seconds % 60) except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_hardware_version(self): try: return self.SBrickPeripheral.readCharacteristic(0x000c).decode("utf-8") except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_software_version(self): try: return self.SBrickPeripheral.readCharacteristic(0x000a).decode("utf-8") except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_brick_id(self): with self.lock: try: self.characteristicRemote.write(b'\x0a') value = self.characteristicRemote.read() return "%0X %0X %0X %0X %0X %0X" % ( value[0], value[1], value[2], value[3], value[4], value[5]) except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_need_authentication(self): with self.lock: try: self.characteristicRemote.write(b'\x02') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_is_authenticated(self): with self.lock: try: self.characteristicRemote.write(b'\x03') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_user_id(self): with self.lock: try: self.characteristicRemote.write(b'\x04') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_error", ex.message) def authenticate_owner(self, password): with self.lock: try: self.authenticated = False cmd = bytearray([0x05, 0x00]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) self.authenticated = True except BTLEException as ex: self.emit("sbrick_error", ex.message) def authenticate_guest(self, password): with self.lock: try: self.authenticated = False cmd = bytearray([0x05, 0x01]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) self.authenticated = True except BTLEException as ex: self.emit("sbrick_error", ex.message) def clear_owner_password(self): with self.lock: try: self.characteristicRemote.write(b'\x06\x00') except BTLEException as ex: self.emit("sbrick_error", ex.message) def clear_guest_password(self): with self.lock: try: self.characteristicRemote.write(b'\x06\x01') except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_owner_password(self, password): with self.lock: try: cmd = bytearray([0x07, 0x00]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_guest_password(self, password): with self.lock: try: cmd = bytearray([0x07, 0x01]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_authentication_timeout(self, seconds): with self.lock: try: cmd = bytearray([0x08, seconds / 0.1]) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message)
def run(self): logging.debug('[%-12s] with args(%s) and kwargs(%s)', 'startup', self.args, self.kwargs) #check args if (self.devid is None): return 1 if (self.addr is None): return 2 global ConfigData global ThreadData p = Empty while True: try: if p is Empty: raise btle.BTLEException(btle.BTLEException.DISCONNECTED, 'BeforeConnect') # receive notification p.waitForNotifications(1.0) # except btle.BTLEException as e: except (btle.BTLEException, Exception) as e: if e.message is not 'BeforeConnect': logging.debug('[%-12s] to addr(%s)', 'disconnected', self.addr) # connection try: p = Peripheral(self.addr) # read the current status data = p.readCharacteristic(0x002a) isclose = int(binascii.b2a_hex(data)) self.lastIsClose = isclose # to db thread if (ConfigData["sqlite"]["use"] == "1"): self.sendDbQueue(isclose, 0, 'insert') # to firebase thread if (ConfigData["firebase"]["use"] == "1"): self.sendFbQueue(isclose, 0, 'insert') # start notification p.setDelegate(NotificationDelegate(self.indexTh)) p.writeCharacteristic(0x002b, "\x01\x00", True) ThreadData['sensor'][self.indexTh]['peripheral'] = p logging.debug('[%-12s] to addr(%s)', 'connected', self.addr) except (btle.BTLEException, Exception) as e: logging.debug('[%-12s] to addr(%s) msg=%s', 'cannot connect', self.addr, e.message) time.sleep(1) # check message from other thread if not self.queueBuf.empty(): msg = self.queueBuf.get() if msg is None: loggin.debug("thread None") else: if (msg.cmd == 'request'): logging.debug( '[%-12s] from(type) = %s, from(no) = %s, cmd = %-10s', 'queue rcvd', msg.fromThreadType, msg.fromThreadNo, msg.cmd) global ThreadType if (msg.fromThreadType == ThreadType['db']): self.sendDbQueue(self.lastIsClose, 3, 'response') elif (msg.fromThreadType == ThreadType['firebase']): self.sendFbQueue(self.lastIsClose, 3, 'response') elif (msg.cmd == 'reget'): logging.debug( '[%-12s] from(type) = %s, from(no) = %s, cmd = %-10s', 'queue rcvd', msg.fromThreadType, msg.fromThreadNo, msg.cmd) # read the current status if p is not Empty: data = p.readCharacteristic(0x002a) isclose = int(binascii.b2a_hex(data)) self.lastIsClose = isclose self.sendDbQueue(isclose, 0, 'insert') self.sendFbQueue(isclose, 0, 'insert') elif (msg.cmd == 'heartbeat'): ThreadData['sensor'][self.devindex]['heartbeat'] += 1 # logging.debug('[%-12s] heartbeat = %s', 'heartbeat update', ThreadData['sensor'][self.devindex]['heartbeat']) elif (msg.cmd == 'exit'): break
p = Peripheral(args.BLEaddress,addrType="random") except BTLEException as ex: print('cannot connect') exit() else: print('connected ',args.BLEaddress) reporter = StatsReporter( # intiate socket (socket.AF_UNIX, ), '/tmp/telegraf.sock', socket.SOCK_DGRAM) atexit.register(reporter.close_socket) # exit routine while True: result = p.readCharacteristic(0x15) # bluetoon fetch and send to socket # Unpack into variables, skipping bytes 0-2 i = 3 PctCharged, V1Volts, V2Volts,Current, Power, Temperature, PowerMeter, ChargeMeter, TimeSinceStart, CurrentTime, PeakCurrent = struct.unpack_from('<BfffffqqIIf', result, i) # Clean up vars PctCharged = PctCharged/2 PowerMeter = PowerMeter/1000 ChargeMeter = ChargeMeter/1000 # Format and send message to socket - not sending V2Volts, TimeSinceStart, Currentime as influxdb as timestamp message = ("meter,volts,amps,watts,temp,kwh,ah,peak\r\n%s,%0.3f,%0.2f,%0.2f,%0.1f,%0.4f,%0.2f,%0.2f" % (meter,V1Volts,Current,Power,Temperature,PowerMeter,ChargeMeter,PeakCurrent)) #print(message) reporter.send_data(message) time.sleep(z)
class RileyLink(PacketRadio): def __init__(self): self.peripheral = None self.pa_level_index = PA_LEVELS.index(0x84) self.data_handle = None self.logger = getLogger() self.packet_logger = get_packet_logger() self.address = g_rl_address self.service = None self.response_handle = None self.notify_event = Event() self.initialized = False self.manchester = ManchesterCodec() def connect(self, force_initialize=False): try: if self.address is None: self.address = self._findRileyLink() if self.peripheral is None: self.peripheral = Peripheral() try: state = self.peripheral.getState() if state == "conn": return except BTLEException: pass self._connect_retry(3) self.service = self.peripheral.getServiceByUUID( RILEYLINK_SERVICE_UUID) data_char = self.service.getCharacteristics( RILEYLINK_DATA_CHAR_UUID)[0] self.data_handle = data_char.getHandle() char_response = self.service.getCharacteristics( RILEYLINK_RESPONSE_CHAR_UUID)[0] self.response_handle = char_response.getHandle() response_notify_handle = self.response_handle + 1 notify_setup = b"\x01\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) while self.peripheral.waitForNotifications(0.05): self.peripheral.readCharacteristic(self.data_handle) if self.initialized: self.init_radio(force_initialize) else: self.init_radio(True) except BTLEException as be: if self.peripheral is not None: self.disconnect() raise PacketRadioError("Error while connecting") from be except Exception as e: raise PacketRadioError("Error while connecting") from e def disconnect(self, ignore_errors=True): try: if self.peripheral is None: self.logger.info("Already disconnected") return self.logger.info("Disconnecting..") if self.response_handle is not None: response_notify_handle = self.response_handle + 1 notify_setup = b"\x00\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) except Exception as e: if not ignore_errors: raise PacketRadioError("Error while disconnecting") from e finally: try: if self.peripheral is not None: self.peripheral.disconnect() self.peripheral = None except BTLEException as be: if ignore_errors: self.logger.exception( "Ignoring btle exception during disconnect") else: raise PacketRadioError("Error while disconnecting") from be except Exception as e: raise PacketRadioError("Error while disconnecting") from e def get_info(self): try: self.connect() bs = self.peripheral.getServiceByUUID(XGATT_BATTERYSERVICE_UUID) bc = bs.getCharacteristics(XGATT_BATTERY_CHAR_UUID)[0] bch = bc.getHandle() battery_value = int(self.peripheral.readCharacteristic(bch)[0]) self.logger.debug("Battery level read: %d", battery_value) version, v_major, v_minor = self._read_version() return { "battery_level": battery_value, "mac_address": self.address, "version_string": version, "version_major": v_major, "version_minor": v_minor } except Exception as e: raise PacketRadioError("Error communicating with RileyLink") from e finally: self.disconnect() def _read_version(self): global g_rl_version, g_rl_v_major, g_rl_v_minor version = None try: if g_rl_version is not None: return g_rl_version, g_rl_v_major, g_rl_v_minor else: response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) g_rl_version = version if version is None: return "0.0", 0, 0 try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError( "Failed to parse firmware version string: %s" % version) g_rl_v_major = int(m.group(1)) g_rl_v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (g_rl_v_major, g_rl_v_minor)) return g_rl_version, g_rl_v_major, g_rl_v_minor except Exception as ex: raise PacketRadioError( "Failed to parse firmware version string: %s" % version) from ex except PacketRadioError: raise except Exception as e: raise PacketRadioError("Error while reading version") from e def init_radio(self, force_init=False): try: version, v_major, v_minor = self._read_version() if v_major < 2: self.logger.error("Firmware version is below 2.0") raise PacketRadioError( "Unsupported RileyLink firmware %d.%d (%s)" % (v_major, v_minor, version)) if not force_init: if v_major == 2 and v_minor < 3: response = self._command(Command.READ_REGISTER, bytes([Register.PKTLEN, 0x00])) else: response = self._command(Command.READ_REGISTER, bytes([Register.PKTLEN])) if response is not None and len( response) > 0 and response[0] == 0x50: return self._command(Command.RADIO_RESET_CONFIG) self._command(Command.SET_SW_ENCODING, bytes([Encoding.NONE])) self._command(Command.SET_PREAMBLE, bytes([0x66, 0x65])) #self._command(Command.SET_PREAMBLE, bytes([0, 0])) frequency = int(433910000 / (24000000 / pow(2, 16))) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, frequency & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, (frequency >> 8) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, (frequency >> 16) & 0xff])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, 0x5f])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, 0x14])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, 0x12])) self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) # self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x20])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTLEN, 0x50])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x60])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x04])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xCA])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xBC])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x70])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xDA])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xB5])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x12])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x23])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 0x31])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST2, 0x81])) ## register not defined on RL # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 0x35])) # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) self._command( Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) #self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) #self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) response = self._command(Command.GET_STATE) if response != b"OK": raise PacketRadioError( "Rileylink state is not OK. Response returned: %s" % response) self.initialized = True except Exception as e: raise PacketRadioError( "Error while initializing rileylink radio: %s", e) def tx_up(self): try: if self.pa_level_index < len(PA_LEVELS) - 1: self.pa_level_index += 1 self._set_amp(self.pa_level_index) except Exception as e: raise PacketRadioError("Error while setting tx up") from e def tx_down(self): try: if self.pa_level_index > 0: self.pa_level_index -= 1 self._set_amp(self.pa_level_index) except Exception as e: raise PacketRadioError("Error while setting tx down") from e def set_tx_power(self, tx_power): try: if tx_power is None: return elif tx_power == TxPower.Lowest: self._set_amp(0) elif tx_power == TxPower.Low: self._set_amp(PA_LEVELS.index(0x1D)) elif tx_power == TxPower.Normal: self._set_amp(PA_LEVELS.index(0x84)) elif tx_power == TxPower.High: self._set_amp(PA_LEVELS.index(0xC8)) elif tx_power == TxPower.Highest: self._set_amp(PA_LEVELS.index(0xC0)) except Exception as e: raise PacketRadioError("Error while setting tx level") from e def get_packet(self, timeout=5.0): try: self.connect() result = self._command(Command.GET_PACKET, struct.pack(">BL", 0, int(timeout * 1000)), timeout=float(timeout) + 0.5) if result is not None: return result[0:2] + self.manchester.decode(result[2:]) else: return None except Exception as e: raise PacketRadioError("Error while getting radio packet") from e def send_and_receive_packet(self, packet, repeat_count, delay_ms, timeout_ms, retry_count, preamble_ext_ms): try: self.connect() data = self.manchester.encode(packet) result = self._command( Command.SEND_AND_LISTEN, struct.pack(">BBHBLBH", 0, repeat_count, delay_ms, 0, timeout_ms, retry_count, preamble_ext_ms) + data, timeout=30) if result is not None: return result[0:2] + self.manchester.decode(result[2:]) else: return None except Exception as e: raise PacketRadioError( "Error while sending and receiving data") from e def send_packet(self, packet, repeat_count, delay_ms, preamble_extension_ms): try: self.connect() data = self.manchester.encode(packet) result = self._command( Command.SEND_PACKET, struct.pack(">BBHH", 0, repeat_count, delay_ms, preamble_extension_ms) + data, timeout=30) return result except Exception as e: raise PacketRadioError("Error while sending data") from e def _set_amp(self, index=None): try: if index is not None: previous_level = self.pa_level_index self.pa_level_index = index if PA_LEVELS[previous_level] == PA_LEVELS[index]: return self.connect() self._command( Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self.packet_logger.debug("Setting pa to %02X (%d of %d)" % (PA_LEVELS[self.pa_level_index], self.pa_level_index, len(PA_LEVELS))) except PacketRadioError: self.logger.exception("Error while setting tx amplification") raise def _findRileyLink(self): global g_rl_address scanner = Scanner() g_rl_address = None self.logger.debug("Scanning for RileyLink") retries = 10 while g_rl_address is None and retries > 0: retries -= 1 for result in scanner.scan(1.0): if result.getValueText(7) == RILEYLINK_SERVICE_UUID: self.logger.debug("Found RileyLink") g_rl_address = result.addr if g_rl_address is None: raise PacketRadioError("Could not find RileyLink") return g_rl_address def _connect_retry(self, retries): while retries > 0: retries -= 1 self.logger.info("Connecting to RileyLink, retries left: %d" % retries) try: self.peripheral.connect(self.address) self.logger.info("Connected") break except BTLEException as btlee: self.logger.warning("BTLE exception trying to connect: %s" % btlee) try: p = subprocess.Popen(["ps", "-A"], stdout=subprocess.PIPE) out, err = p.communicate() for line in out.splitlines(): if "bluepy-helper" in line: pid = int(line.split(None, 1)[0]) os.kill(pid, 9) break except: self.logger.warning("Failed to kill bluepy-helper") time.sleep(1) def _command(self, command_type, command_data=None, timeout=10.0): try: if command_data is None: data = bytes([1, command_type]) else: data = bytes([len(command_data) + 1, command_type ]) + command_data self.peripheral.writeCharacteristic(self.data_handle, data, withResponse=True) if not self.peripheral.waitForNotifications(timeout): raise PacketRadioError( "Timed out while waiting for a response from RileyLink") response = self.peripheral.readCharacteristic(self.data_handle) if response is None or len(response) == 0: raise PacketRadioError("RileyLink returned no response") else: if response[0] == Response.COMMAND_SUCCESS: return response[1:] elif response[0] == Response.COMMAND_INTERRUPTED: self.logger.warning("A previous command was interrupted") return response[1:] elif response[0] == Response.RX_TIMEOUT: return None else: raise PacketRadioError( "RileyLink returned error code: %02X. Additional response data: %s" % (response[0], response[1:]), response[0]) except PacketRadioError: raise except Exception as e: raise PacketRadioError("Error executing command") from e
class Sensor(object): """Read data from sensor.""" def __init__(self, mac, interface): LOGGER.debug('Connecting to device %s using interface %d ...', mac, interface) self.peripheral = Peripheral(deviceAddr=mac, iface=interface) self.battery = None self.version = None self.temperature = None self.brightness = None self.moisture = None self.conductivity = None def get_data(self): """Get all data from sensor.""" self._fetch_35() self._fetch_38() def _fetch_38(self): """Get data from characteristic 38.""" result = self.peripheral.readCharacteristic(0x38) self._decode_38(result) def _decode_38(self, result): """Perform byte magic when decoding the data from the sensor.""" self.battery = int.from_bytes(result[0:1], byteorder=BYTEORDER) self.version = result[2:7].decode('ascii') LOGGER.debug('Raw data for char 0x38: %s', self._format_bytes(result)) LOGGER.debug('battery: %d', self.battery) LOGGER.debug('version: %s', self.version) def _fetch_35(self): """Get data from characteristic 35.""" self.peripheral.writeCharacteristic(0x33, bytes([0xA0, 0x1F]), True) result = self.peripheral.readCharacteristic(0x35) LOGGER.debug('Raw data for char 0x35: %s', self._format_bytes(result)) if result == INVALID_DATA: msg = 'invalid data received' LOGGER.error(msg) raise Exception(msg) self._decode_35(result) def _decode_35(self, result): """Perform byte magic when decoding the data from the sensor.""" # negative numbers are stored in one's complement temp_bytes = result[0:2] if temp_bytes[1] & 0x80 > 0: temp_bytes = [temp_bytes[0] ^ 0xFF, temp_bytes[1] ^ 0xFF] # the temperature needs to be scaled by factor of 0.1 self.temperature = int.from_bytes(temp_bytes, byteorder=BYTEORDER) / 10.0 self.brightness = int.from_bytes(result[3:5], byteorder=BYTEORDER) self.moisture = int.from_bytes(result[7:8], byteorder=BYTEORDER) self.conductivity = int.from_bytes(result[8:10], byteorder=BYTEORDER) LOGGER.debug('temp: %f', self.temperature) LOGGER.debug('brightness: %d', self.brightness) LOGGER.debug('conductivity: %d', self.conductivity) LOGGER.debug('moisture: %d', self.moisture) @staticmethod def _format_bytes(raw_data): """Prettyprint a byte array.""" return ' '.join([format(c, "02x") for c in raw_data]) def factory_reset(self): """Wipe all characteristics with zeros.""" for char in range(0, 0x40): try: print('wiping characteristic {}'.format(char)) self.peripheral.writeCharacteristic(char, bytes([0, 0, 0, 0]), False) except BTLEException: pass
class BluepyBackend(AbstractBackend): """Backend for Miflora using the bluepy library.""" def __init__(self, adapter: str = 'hci0', address_type: str = 'public'): """Create new instance of the backend.""" super(BluepyBackend, self).__init__(adapter, address_type) self._peripheral = None @wrap_exception def connect(self, mac: str): """Connect to a device.""" from bluepy.btle import Peripheral match_result = re.search(r'hci([\d]+)', self.adapter) if match_result is None: raise BluetoothBackendException( 'Invalid pattern "{}" for BLuetooth adpater. ' 'Expetected something like "hci0".'.format(self.adapter)) iface = int(match_result.group(1)) self._peripheral = Peripheral(mac, iface=iface, addrType=self.address_type) @wrap_exception def disconnect(self): """Disconnect from a device if connected.""" if self._peripheral is None: return self._peripheral.disconnect() self._peripheral = None @wrap_exception def read_handle(self, handle: int) -> bytes: """Read a handle from the device. You must be connected to do this. """ if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.readCharacteristic(handle) @wrap_exception def write_handle(self, handle: int, value: bytes): """Write a handle from the device. You must be connected to do this. """ if self._peripheral is None: raise BluetoothBackendException('not connected to backend') return self._peripheral.writeCharacteristic(handle, value, True) @wrap_exception def wait_for_notification(self, handle: int, delegate, notification_timeout: float): if self._peripheral is None: raise BluetoothBackendException('not connected to backend') self.write_handle(handle, self._DATA_MODE_LISTEN) self._peripheral.withDelegate(delegate) return self._peripheral.waitForNotifications(notification_timeout) @wrap_exception def wait_for_notification_no_write(self, handle: int, delegate, notification_timeout: float): if self._peripheral is None: raise BluetoothBackendException('not connected to backend') self._peripheral.withDelegate(delegate) return self._peripheral.waitForNotifications(notification_timeout) @staticmethod def supports_scanning() -> bool: return True @staticmethod def check_backend() -> bool: """Check if the backend is available.""" try: import bluepy.btle # noqa: F401 #pylint: disable=unused-import return True except ImportError as importerror: _LOGGER.error('bluepy not found: %s', str(importerror)) return False @staticmethod @wrap_exception def scan_for_devices(timeout: float, adapter='hci0') -> List[Tuple[str, str]]: """Scan for bluetooth low energy devices. Note this must be run as root!""" from bluepy.btle import Scanner match_result = re.search(r'hci([\d]+)', adapter) if match_result is None: raise BluetoothBackendException( 'Invalid pattern "{}" for BLuetooth adpater. ' 'Expetected something like "hci0".'.format(adapter)) iface = int(match_result.group(1)) scanner = Scanner(iface=iface) result = [] for device in scanner.scan(timeout): result.append((device.addr, device.getValueText(9))) return result
def process_commands(self, command_list): print("Connecting to {}".format(self._mac)) skey = '{}_semaphore'.format(self._mac) with ble_map_lock: if skey not in ble_dev_map: ble_dev_map[skey] = Semaphore() with ble_dev_map[skey]: p = Peripheral(self._mac) print(" Connected to {}".format(self._mac)) for command in command_list: print(" Command {}".format(command)) if 'action' in command: action = command['action'] handle = None if 'handle' in command: handle = int(command['handle']) uuid = None if 'uuid' in command: uuid = command['uuid'] ignoreError = None if 'ignoreError' in command: ignoreError = 1 if 'value' in command: value = command['value'] if type(value) is str: value = value.encode('utf-8') elif type(value) is list: value = bytes(value) try: if action == 'writeCharacteristic': if handle is not None: print(" Write {} to {:02x}".format( value, handle)) p.writeCharacteristic(handle, value, True) elif uuid is not None: for c in p.getCharacteristics(uuid=uuid): print(" Write {} to {}".format( value, uuid)) c.write(value, True) elif action == 'readCharacteristic': if handle is not None: result = p.readCharacteristic(handle) print(" Read {} from {}".format( str(result), handle)) client.publish( 'ble/{}/data/{:02x}'.format( self._mac, handle), json.dumps([int(x) for x in result])) elif uuid is not None: for c in p.getCharacteristics(uuid=uuid): result = c.read() print(" Read {} from {}".format( str(result), uuid)) client.publish( 'ble/{}/data/{}'.format( self._mac, uuid), json.dumps([int(x) for x in result])) except Exception as e: if not ignoreError: raise e p.disconnect()