示例#1
0
    async def getMode(self,
                      address,
                      scanDuration=3) -> CrownstoneOperationMode:
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode.

        We will return once we know.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        """
        _LOGGER.debug(f"getMode address={address} scanDuration={scanDuration}")
        checker = ModeChecker(address, None)
        subscriptionId = BleEventBus.subscribe(
            BleTopics.rawAdvertisement,
            lambda scanData: checker.handleAdvertisement(scanData))
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )

        return result
示例#2
0
    async def _getNearest(self, setup, rssiAtLeast, scanDuration,
                          returnFirstAcceptable, validated,
                          addressesToExclude) -> ScanData or None:
        addressesToExcludeSet = set()
        if addressesToExclude is not None:
            for data in addressesToExclude:
                if hasattr(data, 'address'):
                    addressesToExcludeSet.add(data.address.lower())
                elif isinstance(data, dict):
                    if "address" in data:
                        addressesToExcludeSet.add(data["address"].lower())
                    else:
                        raise CrownstoneException(
                            CrownstoneError.INVALID_ADDRESS,
                            "Addresses to Exclude is either an array of addresses (like 'f7:19:a4:ef:ea:f6') or an array of dicts with the field 'address'"
                        )
                else:
                    addressesToExcludeSet.add(data.lower())

        selector = NearestSelector(setup, rssiAtLeast, returnFirstAcceptable,
                                   addressesToExcludeSet)

        topic = BleTopics.advertisement
        if not validated:
            topic = BleTopics.rawAdvertisement

        subscriptionId = BleEventBus.subscribe(
            topic, lambda scanData: selector.handleAdvertisement(scanData))

        await self.ble.scan(duration=scanDuration)

        BleEventBus.unsubscribe(subscriptionId)

        return selector.getNearest()
示例#3
0
    async def waitForMode(self,
                          address,
                          requiredMode: CrownstoneOperationMode,
                          scanDuration=5):
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode. We will
        scan for the scanDuration amount of seconds or until the Crownstone is in the required mode.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        - BleError.DIFFERENT_MODE_THAN_REQUIRED
            During the {scanDuration} seconds of scanning, the Crownstone was not in the required mode.
        """
        _LOGGER.debug(
            f"waitForMode address={address} requiredMode={requiredMode} scanDuration={scanDuration}"
        )
        checker = ModeChecker(address, requiredMode, True)
        subscriptionId = BleEventBus.subscribe(
            BleTopics.rawAdvertisement,
            lambda scanData: checker.handleAdvertisement(scanData))
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )
        if result != requiredMode:
            raise CrownstoneBleException(
                BleError.DIFFERENT_MODE_THAN_REQUIRED,
                f'During the {scanDuration} seconds of scanning, the Crownstone was not in the required mode..'
            )
示例#4
0
    async def isCrownstoneInSetupMode(self,
                                      address: str,
                                      scanDuration=3,
                                      waitUntilInSetupMode=False) -> bool:
        _LOGGER.warning(
            "isCrownstoneInSetupMode is deprecated. Will be removed in v3. Use either getMode or waitForMode instead."
        )
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode.
        With default value for waitUntilInSetupMode (False), it will return True if the Crownstone is in setup mode, False if it isn't.

        You can use the boolean waitUntilInSetupMode to have it ignore advertisements from this Crownstone in other modes than setup mode.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        """
        _LOGGER.debug(
            f"isCrownstoneInSetupMode address={address} scanDuration={scanDuration} waitUntilInSetupMode={waitUntilInSetupMode}"
        )
        checker = ModeChecker(address, CrownstoneOperationMode.SETUP,
                              waitUntilInSetupMode)
        subscriptionId = BleEventBus.subscribe(BleTopics.advertisement,
                                               checker.handleAdvertisement)
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )

        return result
示例#5
0
 async def getRssiAverage(self, address, scanDuration=3):
     checker = RssiChecker(address)
     subscriptionId = BleEventBus.subscribe(
         BleTopics.rawAdvertisement,
         lambda scanData: checker.handleAdvertisement(scanData))
     await self.ble.scan(duration=scanDuration)
     BleEventBus.unsubscribe(subscriptionId)
     return checker.getResult()
示例#6
0
 async def getCrownstonesByScanning(self, scanDuration=3):
     gatherer = Gatherer()
     subscriptionIdAll = BleEventBus.subscribe(
         BleTopics.rawAdvertisement,
         lambda scanData: gatherer.handleAdvertisement(scanData))
     await self.ble.scan(duration=scanDuration)
     BleEventBus.unsubscribe(subscriptionIdAll)
     return gatherer.getCollection()
示例#7
0
    def handleAdvertisement(self, scanData: ScanData):
        if scanData.address != self.address:
            return
        self.result = scanData.operationMode

        if self.targetMode is not None and self.result != self.targetMode and self.waitUntilInTargetMode:
            pass
        elif self.targetMode is None and self.result == CrownstoneOperationMode.UNKNOWN:
            # if we're looking for a mode, we'll wait for the duration of the timeout in the hope it will be something other than unknown
            pass
        else:
            BleEventBus.emit(SystemBleTopics.abortScanning, True)
示例#8
0
    def parsePayload(self, address, rssi, nameText, serviceDataArray, serviceUUID):
        advertisement = Advertisement(address, rssi, nameText, serviceDataArray, serviceUUID)
        if advertisement.isCrownstoneFamily():
            if advertisement.operationMode == CrownstoneOperationMode.SETUP:
                advertisement.parse()
                BleEventBus.emit(SystemBleTopics.rawAdvertisementClass, advertisement)
            else:
                try:
                    advertisement.parse(self.settings.serviceDataKey)
                except:
                    # fail silently. If we can't parse this, we just to propagate this message
                    pass

                BleEventBus.emit(SystemBleTopics.rawAdvertisementClass, advertisement)
示例#9
0
    def __init__(self,
                 settings: EncryptionSettings,
                 bleAdapterAddress: str = None):
        # bleAdapterAddress is the MAC address of the adapter you want to use.

        self.settings = settings
        self.bleAdapterAddress = bleAdapterAddress

        # Connection
        self.activeClient: ActiveClient or None = None

        # Scanning
        self.scanner = BleakScanner(adapter=bleAdapterAddress)
        self.scanningActive = False
        self.scanAborted = False
        scanDelegate = BleakScanDelegate(self.settings)
        self.scanner.register_detection_callback(scanDelegate.handleDiscovery)

        # Event bus
        self.subscriptionIds = []
        self.validator = Validator()
        self.subscriptionIds.append(
            BleEventBus.subscribe(SystemBleTopics.abortScanning,
                                  lambda x: self.abortScan()))

        # To be moved to active client or notification handler.
        self.notificationLoopActive = False
示例#10
0
    def handleAdvertisement(self, scanData: ScanData):
        if scanData.address in self.addressesToExcludeSet:
            return

        if self.setupModeOnly and not scanData.operationMode == CrownstoneOperationMode.SETUP:
            return

        # TODO: this is actually normalModeOnly, maybe change setupModeOnly to operationMode to filter for.
        if not self.setupModeOnly and scanData.operationMode == CrownstoneOperationMode.SETUP:
            return

        if scanData.rssi < self.rssiAtLeast:
            return

        self.deviceList.append(scanData)

        if self.returnFirstAcceptable:
            BleEventBus.emit(SystemBleTopics.abortScanning, True)
示例#11
0
    async def waitForPeripheralToDisconnect(self, timeout: int = 10):
        if self.activeClient is not None:
            if await self.activeClient.isConnected():
                waiting = True

                def disconnectListener(data):
                    nonlocal waiting
                    waiting = False

                listenerId = BleEventBus.subscribe(
                    SystemBleTopics.forcedDisconnect, disconnectListener)

                timer = 0
                while waiting and timer < 10:
                    await asyncio.sleep(0.1)
                    timer += 0.1

                BleEventBus.unsubscribe(listenerId)

                self.activeClient = None
示例#12
0
    def checkAdvertisement(self, advertisement):
        self.cleanupExpiredTrackers()

        if advertisement.address not in self.trackedCrownstones:
            self.trackedCrownstones[
                advertisement.address] = StoneAdvertisementTracker(
                    lambda: self.removeStone(advertisement.address))

        self.trackedCrownstones[advertisement.address].update(advertisement)

        # forward all scans over this topic. It is located here instead of the delegates so it would be easier to convert the json to classes.
        data = fillScanDataFromAdvertisement(
            advertisement,
            self.trackedCrownstones[advertisement.address].verified)
        BleEventBus.emit(BleTopics.rawAdvertisement, data)
        if self.trackedCrownstones[advertisement.address].verified:
            BleEventBus.emit(BleTopics.advertisement, data)

            if not self.trackedCrownstones[advertisement.address].duplicate:
                BleEventBus.emit(BleTopics.newDataAvailable, data)
示例#13
0
 def __init__(self):
     BleEventBus.subscribe(SystemBleTopics.rawAdvertisementClass,
                           self.checkAdvertisement)
     self.trackedCrownstones = {}
示例#14
0
 def forcedDisconnect(self, data):
     BleEventBus.emit(SystemBleTopics.forcedDisconnect, self.address)
     self.cleanupCallback()
示例#15
0
 async def shutDown(self):
     for subscriptionId in self.subscriptionIds:
         BleEventBus.unsubscribe(subscriptionId)
     await self.disconnect()
     await self.stopScanning()