예제 #1
0
    def handleDiscovery(self, device: ScanEntry, isNew: bool, isData: bool):
        if not isNew:
            return

        device_name = device.getValueText(ScanEntry.COMPLETE_LOCAL_NAME)

        if not isinstance(device_name,
                          str) or not device_name.startswith("Snooz"):
            return

        advertisement = device.getValue(ScanEntry.MANUFACTURER)

        # unexpected data
        if len(advertisement) < 3:
            return

        if advertisement[:3] == b"\xFF\xFF\x08":
            _LOGGER.warning(
                f"Discovered {device_name} ({device.addr}) but it is not in pairing mode."
            )
            return

        token = b64encode(advertisement[3:]).decode("UTF-8")

        snooz_device = DiscoveredSnooz(device_name, token, device)

        self.devices.append(snooz_device)
예제 #2
0
    def _update(self, resp):
        ScanEntry._update(self, resp)

        if self.addrType is "random":
            self.vendor = "None (Random MAC address)"
        else:
            self.vendor = lookup_vendor(self.addr)

        if self.scanData:
            self.data = self._get_data(self.getScanData())
예제 #3
0
    def _update(self, resp):
        ScanEntry._update(self, resp)

        if self.addrType is "random":
            self.vendor = "None (Random MAC address)"
        else:
            self.vendor = lookup_vendor(self.addr)

        if self.scanData:
            self.data = self._get_data(self.getScanData())
예제 #4
0
    def gatherAverageRSSI(self, manufacturer, n_samples, passive=False):
        self.clear()
        self.start(passive=passive)

        rssi_scans = []

        while len(rssi_scans) < n_samples:
            # wait 3 seconds before a timeout
            resp = self._waitResp(['scan', 'stat'], 3.0)
            #                                        ^

            if resp is None:
                break

            respType = resp['rsp'][0]
            if respType == 'stat':
                # if scan ended, restart it
                if resp['state'][0] == 'disc':
                    self._mgmtCmd("scan")

            elif respType == 'scan':
                # device found
                addr = binascii.b2a_hex(resp['addr'][0]).decode('utf-8')
                addr = ':'.join([addr[i:i + 2] for i in range(0, 12, 2)])
                if addr in self.scanned:
                    dev = self.scanned[addr]
                else:
                    dev = ScanEntry(addr, self.iface)
                    self.scanned[addr] = dev
                isNewData = dev._update(resp)

                for (adtype, desc, value) in dev.getScanData():
                    if desc == "Manufacturer" and value == manufacturer:
                        rssi_scans.append(dev.rssi)

            else:
                raise BTLEException(BTLEException.INTERNAL_ERROR,
                                    "Unexpected response: " + respType)

        mean_rssi = mean(rssi_scans)

        std_dev = stdev(rssi_scans)

        cutoff_rssi = []

        lower_cutoff = mean_rssi - std_dev
        upper_cutoff = mean_rssi + std_dev

        for x in rssi_scans:
            if not (x < lower_cutoff or x > upper_cutoff):
                cutoff_rssi.append(x)

        self.stop()
        return mean(cutoff_rssi)
예제 #5
0
 def buildFromScanEntry(scanEntry: ScanEntry) -> Optional["OralBAdvertise"]:
     vendorSpecificData = scanEntry.getValueText(ScanEntry.MANUFACTURER)
     parser = OralBAdvertise(vendorSpecificData)
     if parser.isValid:
         return parser
     else:
         return None
예제 #6
0
파일: scan.py 프로젝트: zhengxyzw/bleah
    def _find_or_create(self, addr):
        if addr in self.scanned:
            dev = self.scanned[addr]
        else:
            dev = ScanEntry(addr, self.iface)
            self.scanned[addr] = dev

        return dev
예제 #7
0
    def _get_dev_name_from_scan_entry(self, dev: ScanEntry):
        dev_name = None

        # Attempt to load from Scandata
        for scan_data in dev.getScanData():
            if scan_data[1] == 'Short Local Name':
                dev_name = scan_data[2]

        # Iterate through the dataTags if the above fails
        if dev_name is None:
            try:
                for idx, label in scan_data.dataTags:
                    if label == 'Short Local Name':
                        dev_name = dev.getValueText(idx)
            except AttributeError:
                pass

        # Load using handles if the above fails
        if dev_name is None:
            dev_name = dev.getValueText(8)

        return dev_name
예제 #8
0
    def process(self, timeout=10.0):
        if self._helper is None:
            raise BTLEException(BTLEException.INTERNAL_ERROR,
                                "Helper not started (did you call start()?)")
        start = time.time()
        while True:
            if timeout:
                remain = start + timeout - time.time()
                if remain <= 0.0:
                    break
            else:
                remain = None
            resp = self._waitResp(['scan', 'stat'], remain)
            if resp is None:
                break

            respType = resp['rsp'][0]
            if respType == 'stat':
                # if scan ended, restart it
                if resp['state'][0] == 'disc':
                    self._mgmtCmd("scan")

            elif respType == 'scan':
                # device found
                addr = binascii.b2a_hex(resp['addr'][0]).decode('utf-8')
                addr = ':'.join([addr[i:i + 2] for i in range(0, 12, 2)])
                if addr in self.scanned:
                    dev = self.scanned[addr]
                else:
                    dev = ScanEntry(addr, self.iface)
                    self.scanned[addr] = dev
                isNewData = dev._update(resp)
                if self.delegate is not None:
                    self.delegate.handleDiscovery(dev, (dev.updateCount <= 1),
                                                  isNewData)

            else:
                raise BTLEException(BTLEException.INTERNAL_ERROR,
                                    "Unexpected response: " + respType)
예제 #9
0
    def __init__(self, addr, iface):
        ScanEntry.__init__(self, addr, iface)

        self.vendor = None
        self.data = []
예제 #10
0
from unittest import TestCase
from time import time
from bluepy.btle import ScanEntry
from scripts.scan import AdvertisingPacket
from scripts.beacon_container import BeaconContainer

scan_entry = ScanEntry('7e:96:b0:dc:fc:95', 0)
scan_entry2 = ScanEntry('7e:96:b0:dc:fc:96', 0)
packet = AdvertisingPacket(scan_entry)
packet2 = AdvertisingPacket(scan_entry2)


class TestBeaconContainer(TestCase):
    def test_clean(self):
        container = BeaconContainer()

        packet.updated_at = 0
        packet2.updated_at = time()
        container.insert(packet.addr, packet)
        container.insert(packet2.addr, packet2)
        container.clean()
        self.assertIsNone(container.get('7e:96:b0:dc:fc:95'))
        self.assertIsNotNone(container.get('7e:96:b0:dc:fc:96'))

    def test_iter(self):
        container = BeaconContainer()
        container.insert(packet.addr, packet)
        container.insert(packet2.addr, packet2)
        # print container
        for item in container:
            self.assertIsNotNone(item)
예제 #11
0
    def handleDiscovery(self, dev: ScanEntry, new_dev: bool, new_data: bool) -> None:

        if not dev.addr == self.mac.lower() or not new_dev or not new_data:
            return

        for (sdid, _, data) in dev.getScanData():

            # Mi Body Composition Scale 2 (XMTZC05HM) / Xiaomi Scale 2 (XMTZC02HM)
            if not data.startswith("1b18") or sdid != 22:
                continue

            # 15b in little endian
            #   0-1: identifier?
            #     2: unit
            #     3: control byte
            #   4-5: year
            #     6: month
            #     7: day
            #     8: hour
            #     9: min
            #     10: sec
            #  11-12: impedance
            #  13-14: weight

            # unpack bytes to dictionary
            measured = dict(zip(DATA_KEYS, unpack("<xxBBHBBBBBHH", bytes.fromhex(data))))

            # check if we got a proper measurement
            measurement_stabilized = measured["control"] & (1 << 5)
            impedance_available = measured["control"] & (1 << 1)

            # pick unit
            unit = UNITS.get(measured["unit_id"], None)
            # calc weight based on unit
            weight = measured["weight"] / 100 / 2 if measured["unit_id"] == 2 else measured["weight"] / 100

            # check if we got a proper measurement
            if not all([measurement_stabilized, unit]):
                logging.debug(f"missing data! weight: {weight} | unit: {unit} | impedance: {measured['impedance']}")
                continue

            # create datetime
            measurement_datetime = datetime(
                measured["year"], measured["month"], measured["day"], measured["hour"], measured["min"], measured["sec"]
            )

            # find the current user based on its weight
            if user := self.find_user(weight):

                bm = xbm.BodyMetrics(
                    user[ATTRS.WEIGHT.value],
                    user[ATTRS.HEIGHT.value],
                    user[ATTRS.AGE.value],
                    user[ATTRS.SEX.value],
                    measured["impedance"],
                )

                attributes = {
                    ATTRS.USER.value: user[ATTRS.USER.value],
                    ATTRS.AGE.value: user[ATTRS.AGE.value],
                    ATTRS.SEX.value: user[ATTRS.SEX.value],
                    ATTRS.HEIGHT.value: user[ATTRS.HEIGHT.value],
                    ATTRS.WEIGHT.value: f"{weight:.2f}",
                    ATTRS.UNIT.value: unit,
                    ATTRS.BASAL_METABOLISM.value: f"{bm.get_bmr():.2f}",
                    ATTRS.VISCERAL_FAT.value: f"{bm.getVisceralFat():.2f}",
                    ATTRS.BMI.value: f"{bm.getBMI():.2f}",
                    ATTRS.TIMESTAMP.value: measurement_datetime.isoformat(),
                }

                # if we got a valid impedance, we can add more metrics
                if impedance_available:
                    attributes.update(
                        {
                            ATTRS.WATER.value: f"{bm.getWaterPercentage():.2f}",
                            ATTRS.BONE_MASS.value: f"{bm.getBoneMass():.2f}",
                            ATTRS.BODY_FAT.value: f"{bm.getFatPercentage():.2f}",
                            ATTRS.LEAN_BODY_MASS.value: f"{bm.get_lbm_coefficient():.2f}",
                            ATTRS.MUSCLE_MASS.value: f"{bm.getMuscleMass():.2f}",
                            ATTRS.PROTEIN.value: f"{bm.getProteinPercentage():.2f}",
                        }
                    )

                self.data.update(
                    {
                        "name": PLUGIN_NAME,
                        "sensors": [
                            {
                                "name": f"{self.alias} {user[ATTRS.USER.value]}",
                                "value_template": "{{value_json." + ATTRS.WEIGHT.value + "}}",
                                "entity_type": ATTRS.WEIGHT,
                                "own_state_topic": True,
                            },
                        ],
                        "attributes": attributes,
                    }
                )
예제 #12
0
 def handleDiscovery(self, dev: ScanEntry, isNewDev: bool, isNewData: bool):
     name = dev.getValue(ScanEntry.COMPLETE_LOCAL_NAME) or ""
     toy_type = TOY_BY_PREFIX.get(name[:3])
     if toy_type:
         self.queue.put_nowait(_ScanItem(dev.addr, toy_type, name))
예제 #13
0
    def __init__(self, addr, iface):
        ScanEntry.__init__(self, addr, iface)

        self.vendor = None
        self.data = []
예제 #14
0
 def is_turn_touch(device: btle.ScanEntry) -> bool:
     """Determine whether a ScanEntry (device) is a Turn Touch."""
     short_name = device.getValueText(BLE_SHORT_DEVICE_NAME)
     name = device.getValueText(BLE_COMPLETE_DEVICE_NAME)
     return name == TT_DEVICE_NAME or short_name == TT_SHORT_DEVICE_NAME
예제 #15
0
    def relay_ble_advertisement(self, scanEntry:  ScanEntry): 
        """
        Send the BLE packet to the connected service running on iOS 
        """
        ### Message format 
        # 1 byte type - 4 bytes message length - message 
        ###

        name = scanEntry.getValueText( ScanEntry.COMPLETE_LOCAL_NAME)
        if not name: 
            name = scanEntry.getValueText( ScanEntry.SHORT_LOCAL_NAME)
        
        # services_16 = scanEntry.getValueText(ScanEntry.INCOMPLETE_16B_SERVICES)
        # services_32 = scanEntry.getValueText(ScanEntry.INCOMPLETE_32B_SERVICES)
        # services_128 = scanEntry.getValueText(ScanEntry.INCOMPLETE_128B_SERVICES)
        # print("Services 16B: \n\t{}\nServices 32B: \n\t{}\nServices 128B: \n\t{}".format(services_16, services_32 , services_128 ))
        DBG("Raw Data: {}".format(scanEntry.rawData), logLevel=LogLevel.DEBUG)
        raw_data_hex = ""
        if scanEntry.rawData: 
            raw_data_hex = scanEntry.rawData.hex()

        packet_content = {
            "manufacturerDataHex": scanEntry.getValueText(ScanEntry.MANUFACTURER), 
            "macAddress": scanEntry.addr, 
            "rssi": scanEntry.rssi,
            "name": name, 
            "flags": scanEntry.getValueText( ScanEntry.FLAGS), 
            "addressType": scanEntry.addrType,
            "connectable": scanEntry.connectable, 
            "rawData": raw_data_hex, 
            #"scanData": {tag: scanEntry.getValueText(k) for k, tag in scanEntry.dataTags.items()}
        }

        # Check for additional content in this advertisement 

        # Get service UUIDs 
        serviceUUIDs = list()
        if scanEntry.getValueText(ScanEntry.INCOMPLETE_16B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.INCOMPLETE_16B_SERVICES))

        if scanEntry.getValueText(ScanEntry.INCOMPLETE_32B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.INCOMPLETE_32B_SERVICES))
        
        if scanEntry.getValueText(ScanEntry.INCOMPLETE_128B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.INCOMPLETE_128B_SERVICES))

        if scanEntry.getValueText(ScanEntry.COMPLETE_16B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.COMPLETE_16B_SERVICES))

        if scanEntry.getValueText(ScanEntry.COMPLETE_32B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.COMPLETE_32B_SERVICES))

        if scanEntry.getValueText(ScanEntry.COMPLETE_128B_SERVICES):
            serviceUUIDs.append(scanEntry.getValueText(ScanEntry.COMPLETE_128B_SERVICES))

        if len(serviceUUIDs) > 0: 
            packet_content["serviceUUIDs"] = serviceUUIDs 

        # Get service data 
        if scanEntry.getValueText(ScanEntry.SERVICE_DATA_16B): 
            packet_content["serviceData16Bit"] = scanEntry.getValueText(ScanEntry.SERVICE_DATA_16B)
        
        if scanEntry.getValueText(ScanEntry.SERVICE_DATA_32B): 
            packet_content["serviceData32Bit"] = scanEntry.getValueText(ScanEntry.SERVICE_DATA_32B)
        
        if scanEntry.getValueText(ScanEntry.SERVICE_DATA_128B): 
            packet_content["serviceData128Bit"] = scanEntry.getValueText(ScanEntry.SERVICE_DATA_128B)

        
        DBG("Encoding json: ", packet_content)
        json_packet = json.dumps(packet_content).encode() 
        
        # Message type for advertisements is 0
        self.send_packet_over_socket(packet_type=0, packet_data=json_packet)
예제 #16
0
 def process(self, timeout=300.0):
     """
         Method receives advertisements from nodes. Variable timeout has default value 300 seconds, it define time 
         after which method will end. 
     """
     if self._helper is None:
         raise BTLEException(BTLEException.INTERNAL_ERROR,
                             "Helper not started (did you call start()?)")
     start = time.time()
     while True:
         if timeout:
             remain = start + timeout - time.time()
             if remain <= 0.0:
                 break
         else:
             remain = None
         resp = self._waitResp(['scan', 'stat'], remain)
         if resp is None:
             break
         respType = resp['rsp'][0]
         if respType == 'stat':
             logging.info("STAT message,")
             # if scan ended, restart it
             if resp['state'][0] == 'disc':
                 logging.warning("Executing SCAN cmd!")
                 self._mgmtCmd("scan")
         elif respType == 'scan':
             # device found
             addr = binascii.b2a_hex(resp['addr'][0]).decode('utf-8')
             addr = ':'.join([addr[i:i + 2] for i in range(0, 12, 2)])
             addr = addr.upper()
             if not Device.select(lambda d: d.id.upper() == addr).first(
             ) and not settings.ALLOW_ANY:
                 logging.warning(
                     "Unknown device {} send message, skipping...".format(
                         addr))
                 continue
             dev = ScanEntry(addr, self.iface)
             logging.info("SCAN message from {}".format(addr))
             dev._update(resp)
             if not settings.ALLOW_ANY:
                 name = ''
                 for data in dev.getScanData():
                     for x in data:
                         if type("Name") == type(x) and "Name" in x:
                             name = data[-1]
                 if not settings.NAME_VALIDATION:
                     logging.warning(
                         "Accepting device without name validation...")
                 elif Device[addr].name != name and Device[
                         addr].name != None:
                     logging.warning(
                         "{} invalid name valid: {}, received: {}, skipping..."
                         .format(addr, name, Device[addr].name))
                     continue
             if self.delegate is not None:
                 self.delegate.handleDiscovery(dev, (dev.updateCount <= 1),
                                               True)
         else:
             raise BTLEException(BTLEException.INTERNAL_ERROR,
                                 "Unexpected response: " + respType)