Example #1
0
    async def _writeControlAndGetResult(
        self,
        controlPacket,
        acceptedResultValues=[
            ResultValue.SUCCESS, ResultValue.SUCCESS_NO_CHANGE
        ]
    ) -> ResultPacket:
        """
        Writes the control packet, checks the result value, and returns the result packet.
        @param controlPacket:          Serialized control packet to write.
        @param acceptedResultValues:   List of result values that are ok.
        @return:                       The result packet.
        """
        if self.core.ble.hasCharacteristic(SetupCharacteristics.Result):
            result = await self.core.ble.setupSingleNotification(
                CSServices.SetupService, SetupCharacteristics.Result,
                lambda: self._writeControlPacket(controlPacket))
        else:
            result = await self.core.ble.setupSingleNotification(
                CSServices.CrownstoneService, CrownstoneCharacteristics.Result,
                lambda: self._writeControlPacket(controlPacket))
        resultPacket = ResultPacket(result)
        if not resultPacket.valid:
            raise CrownstoneException(
                CrownstoneError.INCORRECT_RESPONSE_LENGTH, "Result is invalid")
        if resultPacket.resultCode not in acceptedResultValues:
            raise CrownstoneException(
                CrownstoneError.RESULT_NOT_SUCCESS,
                f"Result code is {resultPacket.resultCode}")

        return resultPacket
Example #2
0
    def parse(self, decryptionKey=None):
        if decryptionKey is not None:
            self.decrypt(decryptionKey)

        reader = BufferReader(self._data)
        reader.skip(2)

        if self.opCode == 7:
            try:
                self.payload = parseOpcode7(reader.getRemainingBytes())
            except CrownstoneException:
                self.payload = AdvUnknownData()
                self.payload.data = self._data
                raise CrownstoneException(
                    CrownstoneError.INVALID_SERVICE_DATA,
                    "Protocol not supported. Unknown data type. Could be because decryption failed."
                )
        elif self.opCode == 6:
            self.payload = parseOpCode6(reader.getRemainingBytes())
        else:
            self.payload = AdvUnknownData()
            self.payload.data = self._data
            raise CrownstoneException(
                CrownstoneError.INVALID_SERVICE_DATA,
                "Protocol not supported. Unknown opcode.")
Example #3
0
    async def _command_via_mesh_broadcast(self, packet: bytearray):
        # this is only for time and noop
        # broadcast to all:
        # value: 1
        corePacket = MeshBroadcastPacket(packet).getPacket()
        controlPacket = ControlPacket(
            ControlType.MESH_COMMAND).loadByteArray(corePacket).getPacket()
        uartMessage = UartMessagePacket(UartTxType.CONTROL,
                                        controlPacket).getPacket()
        uartPacket = UartWrapperPacket(UartMessageType.UART_MESSAGE,
                                       uartMessage).getPacket()

        resultCollector = Collector(timeout=2, topic=SystemTopics.resultPacket)

        # send the message to the Crownstone
        UartEventBus.emit(SystemTopics.uartWriteData, uartPacket)

        # wait for the collectors to fill
        commandResultData = await resultCollector.receive()

        if commandResultData is not None:
            if commandResultData.resultCode is ResultValue.BUSY:
                await asyncio.sleep(0.2)
                return await self._command_via_mesh_broadcast(packet)
            elif commandResultData.resultCode is not ResultValue.SUCCESS:
                raise CrownstoneException(commandResultData.resultCode,
                                          "Command has failed.")

        await asyncio.sleep(0.1)
Example #4
0
def get_master_crc_from_filter_crcs(input_data: [[int, int]]) -> int:
    """
    Get the master CRC from filter CRCs.

    :param input_data:  A list of [filterId, filterCRC].
    :returns:           The master CRC.
    """
    def sort_method(val):
        return val[0]

    input_data.sort(key=sort_method)

    # Check for duplicate filter IDs.
    for i in range(0, len(input_data) - 1):
        if input_data[i][0] == input_data[i + 1][0]:
            raise CrownstoneException(
                CrownstoneError.INVALID_INPUT,
                "Cannot have 2 filters with the same ID.")

    writer = BufferWriter()
    for id_and_filter_crc in input_data:
        writer.putUInt8(id_and_filter_crc[0])
        writer.putUInt32(id_and_filter_crc[1])

    return crc32(writer.getBuffer())
Example #5
0
	async def _writeControlAndGetResult(self, controlPacket):
		""" Writes the control packet, and returns the result packet. """
		result = await self.core.ble.setupSingleNotification(CSServices.CrownstoneService, CrownstoneCharacteristics.Result, lambda: self._writeControlPacket(controlPacket))
		resultPacket = ResultPacket(result)
		if not resultPacket.valid:
			raise CrownstoneException(CrownstoneError.INCORRECT_RESPONSE_LENGTH, "Result is invalid")
		return resultPacket
Example #6
0
	async def getUptime(self):
		""" Get the uptime of the crownstone in seconds. """
		controlPacket = ControlPacket(ControlType.GET_UPTIME).getPacket()
		result = await self._writeControlAndGetResult(controlPacket)
		if result.resultCode != ResultValue.SUCCESS:
			raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS, "Result: " + str(result.resultCode))
		return Conversion.uint8_array_to_uint32(result.payload)
 async def getPowerSamplesAtIndex(self, samplesType, index):
     """ Get power samples of given type at given index. Returns a PowerSamplesPacket. """
     result = await self._getPowerSamples(samplesType, index)
     if result.resultCode != ResultValue.SUCCESS:
         raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS,
                                   "Result: " + str(result.resultCode))
     return PowerSamplesPacket(result.payload)
Example #8
0
	async def getAdcChannelSwaps(self):
		""" Get number of ADC channel swaps since boot. Returns an AdcChannelSwapsPacket. """
		controlPacket = ControlPacket(ControlType.GET_ADC_CHANNEL_SWAPS).getPacket()
		result = await self._writeControlAndGetResult(controlPacket)
		if result.resultCode != ResultValue.SUCCESS:
			raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS, "Result: " + str(result.resultCode))
		return AdcChannelSwapsPacket(result.payload)
Example #9
0
	async def getSwitchHistory(self):
		""" Get the switch history. Returns a SwitchHistoryListPacket. """
		controlPacket = ControlPacket(ControlType.GET_SWITCH_HISTORY).getPacket()
		result = await self._writeControlAndGetResult(controlPacket)
		if result.resultCode != ResultValue.SUCCESS:
			raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS, "Result: " + str(result.resultCode))
		return SwitchHistoryListPacket(result.payload)
    def filterByNameWithWildcards(self, name: str, complete: bool = True):
        """
        Assets are filtered by their name (case sensitive).
        :param name:      Name, with wildcards, to be added filter.
                          '?' matches any single character
                          '*' matches zero or more characters, only at the end of a name.
                          Example: "??_dev*" will match:  "my_dev", and "01_device"
                                             won't match: "your_device", or "_dev"
        :param complete:  Whether to look for the complete or shortened name.
        """
        self._resetCache()
        if len(name) > 31:
            raise CrownstoneException(CrownstoneError.INVALID_SIZE,
                                      f"Name is too long: {name}")
        bitmask = 0
        asset_name = ""
        for i in range(0, len(name)):
            if name[i] != '?':
                bitmask = set_bit(bitmask, i)
                asset_name += name[i]
        if name[-1] == '*':
            bitmask = set_bit(bitmask, len(name) - 1, False)
            asset_name = asset_name[:-1]
        else:
            # Set all remaining bits. If there is more data, it will be used as input.
            for i in range(len(name), 32):
                bitmask = set_bit(bitmask, i)

        _LOGGER.info(
            f"name={name} bitmask={bitmask:032b} asset_name={asset_name}")

        adType = 0x09 if complete else 0x08
        self._input = InputDescriptionMaskedAdData(adType, bitmask)
        self._assets = [Conversion.string_to_uint8_array(asset_name)]
        return self
Example #11
0
 async def _checkRecoveryProcess(self):
     result = self.core.ble.readCharacteristicWithoutEncryption(
         CSServices.CrownstoneService,
         CrownstoneCharacteristics.FactoryReset)
     if result[0] == 1:
         return True
     elif result[0] == 2:
         raise CrownstoneException(
             BleError.RECOVERY_MODE_DISABLED,
             "The recovery mechanism has been disabled by the Crownstone owner."
         )
     else:
         raise CrownstoneException(
             BleError.NOT_IN_RECOVERY_MODE,
             "The recovery mechanism has expired. It is only available briefly after the Crownstone is powered on."
         )
Example #12
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()
Example #13
0
 def _checkResult(self, success_codes):
     if success_codes == [] or self.result.resultCode in success_codes:
         self.__del__()
         return self.result
     else:
         raise CrownstoneException(
             "WRITE_EXCEPTION",
             "Incorrect result type. Got " + str(self.result.resultCode) + ", expected one of " + str(success_codes),
             400
         )
Example #14
0
 def _request(self, size):
     if self.position + size <= self.length:
         start = self.position
         self.position += size
         return self.data[start:self.position]
     else:
         raise CrownstoneException(
             CrownstoneError.INVALID_SIZE, f"bufferSize={self.length} "
             f"position={self.position} "
             f"requestedSize={size} "
             f"buffer={self.data}")
Example #15
0
def parseOpCode6(data):
    reader = BufferReader(data)
    dataType = reader.getUInt8()

    if dataType == 0:
        return parseSetupState(reader)
    elif dataType == 5:
        return parseHubData(reader)
    else:
        raise CrownstoneException(CrownstoneError.UNKNOWN_SERVICE_DATA,
                                  "Could not parse this dataType")
Example #16
0
 async def enableMicroapp(self, index):
     packet = MicroappHeaderPacket(index)
     controlPacket = ControlPacket(
         ControlType.MICROAPP_ENABLE).loadByteArray(
             packet.toBuffer()).getPacket()
     result = await self.core.ble.setupSingleNotification(
         CSServices.CrownstoneService, CrownstoneCharacteristics.Result,
         lambda: self._writeControlPacket(controlPacket))
     resultPacket = ResultPacket(result)
     if resultPacket.resultCode != ResultValue.SUCCESS:
         raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS,
                                   f"result={resultPacket.resultCode}")
Example #17
0
	async def getPowerSamples(self, samplesType):
		""" Get all power samples of the given type. Returns a list of PowerSamplesPacket. """
		allSamples = []
		index = 0
		while True:
			result = await self._getPowerSamples(samplesType, index)
			if result.resultCode == ResultValue.WRONG_PARAMETER:
				return allSamples
			elif result.resultCode == ResultValue.SUCCESS:
				samples = PowerSamplesPacket(result.payload)
				allSamples.append(samples)
				index += 1
			else:
				raise CrownstoneException(CrownstoneError.RESULT_NOT_SUCCESS, "Result: " + str(result.resultCode))
Example #18
0
    def decrypt(self, keyHexString):
        if len(keyHexString) >= 16 and len(self._data) == 18:
            if self.operationMode == CrownstoneOperationMode.NORMAL:
                reader = BufferReader(self._data)
                encryptedData = reader.skip(2).getRemainingBytes()
                result = EncryptionHandler.decryptECB(encryptedData,
                                                      keyHexString)

                for i in range(0, len(encryptedData)):
                    # the first 2 bytes are opcode and device type
                    self._data[i + 2] = result[i]

                self.decrypted = True
        else:
            raise CrownstoneException(
                CrownstoneError.COULD_NOT_DECRYPT,
                "ServiceData decryption failed. Invalid key or invalid data.")
Example #19
0
    async def getFilterSummaries(self) -> FilterSummariesPacket:
        """
        Get a summary of the filters that are on the Crownstones.
        This can be used to determine:
        - Which filters should be changed.
        - What the next master version should be.
        - How much space there is left for new filters.
        - The new master CRC.

        :return:   The filter summaries packet.
        """
        _LOGGER.info(f"getFilterSummaries")
        result = await self._write(ControlPacketsGenerator.getGetFilterSummariesPacket())
        if result is None:
            raise CrownstoneException(CrownstoneError.DATA_MISSING, "No summaries received")
        summaries = FilterSummariesPacket(result)
        return summaries
    def add(self, item: list):
        """
        Add an item to the filter.

        :param item: Byte array representation of the item.
        """
        if item in self.items:
            # Avoid duplicates
            return

        if self.itemCount:
            if self.itemSize != len(item):
                raise CrownstoneException(
                    CrownstoneError.INVALID_SIZE,
                    f"Must be same size as other items ({self.itemSize})")
        else:
            self.itemSize = len(item)
        self.items.append(item)
        self.itemCount = len(self.items)
Example #21
0
def parseOpcode7(data):
    reader = BufferReader(data)
    dataType = reader.getUInt8()

    if dataType == 0:
        return parseStatePacket(reader)
    elif dataType == 1:
        return parseErrorPacket(reader)
    elif dataType == 2:
        return parseExternalStatePacket(reader)
    elif dataType == 3:
        return parseExternalErrorPacket(reader)
    elif dataType == 4:
        return parseAlternativeState(reader)
    elif dataType == 5:
        return parseHubData(reader)
    elif dataType == 6:
        return parseMicroappServiceData(reader)
    else:
        raise CrownstoneException(CrownstoneError.UNKNOWN_SERVICE_DATA,
                                  "Could not parse this dataType")
    async def setPowerZero(self, mW: int):
        controlPacket = ControlStateSetPacket(
            StateType.POWER_ZERO).loadInt32(mW).serialize()
        uartMessage = UartMessagePacket(UartTxType.CONTROL,
                                        controlPacket).serialize()
        uartPacket = UartWrapperPacket(UartMessageType.UART_MESSAGE,
                                       uartMessage).serialize()

        resultCollector = Collector(timeout=1, topic=SystemTopics.resultPacket)
        # send the message to the Crownstone
        UartEventBus.emit(SystemTopics.uartWriteData, uartPacket)

        # wait for the collectors to fill
        commandResultData = await resultCollector.receive()

        if commandResultData is not None:
            if commandResultData.resultCode is ResultValue.BUSY:
                await asyncio.sleep(0.2)
                return await self.setPowerZero(mW)
            elif commandResultData.resultCode is not ResultValue.SUCCESS:
                raise CrownstoneException(commandResultData.resultCode,
                                          "Command has failed.")
Example #23
0
    async def _write(self, controlPacket: [int], successCodes = [ResultValue.SUCCESS, ResultValue.SUCCESS_NO_CHANGE, ResultValue.WAIT_FOR_SUCCESS]) -> [int] or None:
        """
        Returns the result payload.
        TODO: return result packet.
        TODO: use a ControlPacket as param, instead of int array.
        """
        _LOGGER.debug(f"Write control packet {controlPacket}")
        uartMessage = UartMessagePacket(UartTxType.CONTROL, controlPacket).serialize()
        uartPacket = UartWrapperPacket(UartMessageType.UART_MESSAGE, uartMessage).serialize()

        resultCollector = Collector(timeout=1, topic=SystemTopics.resultPacket)
        # send the message to the Crownstone
        UartEventBus.emit(SystemTopics.uartWriteData, uartPacket)

        # wait for the collectors to fill
        commandResultData = await resultCollector.receive()

        if commandResultData is not None:
            if commandResultData.resultCode not in successCodes:
                raise CrownstoneException(commandResultData.resultCode, f"Command has failed: result code is {commandResultData.resultCode}")
            return commandResultData.payload
        return None
Example #24
0
    async def _command_via_mesh_broadcast_acked(
            self, crownstone_uid_array: List[int],
            packet: bytearray) -> MeshResult:
        # this is only for the set_iBeacon_config_id
        # broadcast to all, but retry until ID's in list have acked or timeout
        # value: 3
        corePacket = MeshBroadcastAckedPacket(crownstone_uid_array,
                                              packet).getPacket()
        controlPacket = ControlPacket(
            ControlType.MESH_COMMAND).loadByteArray(corePacket).getPacket()
        uartMessage = UartMessagePacket(UartTxType.CONTROL,
                                        controlPacket).getPacket()
        uartPacket = UartWrapperPacket(UartMessageType.UART_MESSAGE,
                                       uartMessage).getPacket()

        resultCollector = Collector(timeout=2, topic=SystemTopics.resultPacket)
        individualCollector = BatchCollector(
            timeout=15, topic=SystemTopics.meshResultPacket)
        finalCollector = Collector(timeout=15,
                                   topic=SystemTopics.meshResultFinalPacket)

        # send the message to the Crownstone
        UartEventBus.emit(SystemTopics.uartWriteData, uartPacket)

        # wait for the collectors to fill
        commandResultData = await resultCollector.receive()
        if commandResultData is not None:
            if commandResultData.resultCode is ResultValue.BUSY:
                await asyncio.sleep(0.2)
                return await self._command_via_mesh_broadcast_acked(
                    crownstone_uid_array, packet)
            elif commandResultData.resultCode is not ResultValue.SUCCESS:
                raise CrownstoneException(commandResultData.resultCode,
                                          "Command has failed.")

        return await self._handleCollectors(crownstone_uid_array,
                                            individualCollector,
                                            finalCollector)
Example #25
0
    async def _set_state_via_mesh_acked(self, crownstone_id: int,
                                        packet: bytearray) -> MeshResult:
        # 1:1 message to N crownstones with acks (only N = 1 supported for now)
        # flag value: 2
        corePacket = MeshSetStatePacket(crownstone_id, packet).getPacket()
        controlPacket = ControlPacket(
            ControlType.MESH_COMMAND).loadByteArray(corePacket).getPacket()
        uartMessage = UartMessagePacket(UartTxType.CONTROL,
                                        controlPacket).getPacket()
        uartPacket = UartWrapperPacket(UartMessageType.UART_MESSAGE,
                                       uartMessage).getPacket()

        resultCollector = Collector(timeout=2, topic=SystemTopics.resultPacket)
        individualCollector = BatchCollector(
            timeout=15, topic=SystemTopics.meshResultPacket)
        finalCollector = Collector(timeout=15,
                                   topic=SystemTopics.meshResultFinalPacket)

        # send the message to the Crownstone
        UartEventBus.emit(SystemTopics.uartWriteData, uartPacket)

        # wait for the collectors to fill
        commandResultData = await resultCollector.receive()

        if commandResultData is not None:
            if commandResultData.resultCode is ResultValue.BUSY:
                await asyncio.sleep(0.2)
                return await self._set_state_via_mesh_acked(
                    crownstone_id, packet)
            elif commandResultData.resultCode is not ResultValue.SUCCESS:
                raise CrownstoneException(commandResultData.resultCode,
                                          "Command has failed.")

        return await self._handleCollectors([crownstone_id],
                                            individualCollector,
                                            finalCollector)
    def build(self) -> AssetFilterPacket:
        # Check variables
        if self._filterId is None:
            raise CrownstoneException(CrownstoneError.DATA_MISSING,
                                      f"No filter ID set.")
        if self._input is None:
            raise CrownstoneException(CrownstoneError.DATA_MISSING,
                                      f"No filter input set.")
        if (self._outputType is None) and (not self._exclude):
            raise CrownstoneException(CrownstoneError.DATA_MISSING,
                                      f"No filter output set.")

        # Determine output description
        outputType = FilterOutputDescriptionType.MAC_ADDRESS
        if self._outputType is not None:
            outputType = self._outputType

        inFormat = None
        if self._assetIdSourceBuilder is not None:
            inFormat = self._assetIdSourceBuilder.build()

        output = FilterOutputDescription(outputType, inFormat)

        # Remove duplicates.
        uniqueAssets = []
        for asset in self._assets:
            if asset not in uniqueAssets:
                uniqueAssets.append(asset)
        self._assets = uniqueAssets

        # Determine filter type to use if it hasn't been set.
        if self._filterType is None:
            equalSize = True
            assetSize = len(self._assets[0])
            totalSize = 0
            for asset in self._assets:
                if len(asset) != assetSize:
                    equalSize = False
                totalSize += len(asset)
            _LOGGER.debug(f"equalSize={equalSize} totalSize={totalSize}")

            filterOverhead = 100  # TODO: this is a very rough estimate.
            if totalSize + filterOverhead < self._maxFilterSize and equalSize:
                self._filterType = FilterType.EXACT_MATCH
            else:
                self._filterType = FilterType.CUCKOO

        # Build the meta data.
        metaData = FilterMetaData(self._filterType, self._input, output,
                                  self._profileId,
                                  FilterFlags(exclude=self._exclude))

        # Construct and fill the filter.
        if self._filterType == FilterType.EXACT_MATCH:
            filterData = ExactMatchFilter()
            for asset in self._assets:
                filterData.add(asset)
        elif self._filterType == FilterType.CUCKOO:
            # TODO: move this to cuckoo filter implementation.
            initialNestsPerBucket = 4
            requiredBucketCount = len(
                self._assets) / 0.95 / initialNestsPerBucket
            bucketCountLog2 = max(0, math.ceil(math.log2(requiredBucketCount)))
            bucketCount = math.pow(2, bucketCountLog2)
            nestsPerBucket = math.ceil(len(self._assets) / bucketCount)

            cuckooFilter = CuckooFilter(bucketCountLog2, nestsPerBucket)
            for asset in self._assets:
                if not cuckooFilter.add(asset):
                    raise CrownstoneException(
                        CrownstoneError.INVALID_SIZE,
                        "Failed to add asset to cuckoo filter.")
            filterData = cuckooFilter.getData()
        else:
            raise CrownstoneException(
                CrownstoneError.UNKNOWN_TYPE,
                f"Unknown filter type: {self._filterType}")

        self._packet = AssetFilterPacket(metaData, filterData)
        self._crc = crc32(self.serialize())
        return self._packet
 def _wrapUpFailedResult(self, wait_until_result):
     self.__del__()
     # TODO: Invalid error type.
     raise CrownstoneException("WRITE_EXCEPTION", "No result received after writing to UART. Waited for " + str(
         wait_until_result) + " seconds", 404)
 def _wrapUpFailedWrite(self):
     # we should never arrive here. If the write went wrong, an error should have been thrown by the write method before we get here.
     self.__del__()
     # TODO: Invalid error type.
     raise CrownstoneException("WRITE_EXCEPTION", "Write not completed, but no error was thrown.", 500)