Beispiel #1
1
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))
Beispiel #4
0
	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);
Beispiel #5
0
    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"))
Beispiel #7
0
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')
Beispiel #8
0
    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()
Beispiel #9
0
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
Beispiel #10
0
    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()
Beispiel #11
0
 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
Beispiel #12
0
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
Beispiel #15
0
  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
Beispiel #16
0
 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
Beispiel #17
0
 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)
Beispiel #18
0
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()
Beispiel #19
0
    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
Beispiel #21
0
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')
Beispiel #22
0
    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
Beispiel #23
0
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
Beispiel #24
0
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))
Beispiel #26
0
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)
Beispiel #28
0
    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
Beispiel #29
0
    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)

Beispiel #30
0
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
Beispiel #31
0
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
Beispiel #32
0
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()