Example #1
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)
Example #2
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
Example #3
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,
                    }
                )
Example #4
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)