Package: crownstone-ble
Crownstone BLE libraryA library to interact with Crownstones via Bluetooth LE.This library uses Bleak as bluetooth backend, which supports Windows, MacOS and Linux.InstallationPython 3.7 or 3.8 is required for this library. At the moment an upstream dependency (pythonnet) is broken (on Windows 10) for Python 3.9 and newer.If you want to use python virtual environments, take a look at theREADME_VENVand be sure that your environment is activated before installing. Afterwards it's as simple as:python3 -m pip install crownstone_bleAsync functionsThis library uses async methods, whichmustbe awaited. This is part of Python and uses the asyncio core module to do this.
If you're unsure about how to use these, there's a million guides and tutorials online. We will assume you know how to use these in the rest of the documentation.CrownstoneBleInitializationTo use Crownstone BLE, you first import it from crownstone_ble.fromcrownstone_bleimportCrownstoneBleble=CrownstoneBle()CrownstoneBle is composed of a number of top level methods and modules for specific commands. We will first describe these top level methods.__init__(bleAdapterAddress=None)When initializing the CrownstoneBle class, you can provide the bluetooth adapter address to choose which bluetooth adapter to use. This only works on linux. You can get these addresses by running:hcitool devThese addresses are in the "00:32:FA:DE:15:02" format.
The constructor is not explicitly called with__init__, but like this:ble=CrownstoneBle(bleAdapterAddress="00:32:FA:DE:15:02")On other platforms you can't define which bluetooth adapter to use.async shutDown()Shuts down the library nicely. This is should be done when closing your script.setSettings(adminKey: string, memberKey: string, basicKey: string, serviceDataKey: string, localizationKey: string, meshApplicationKey: string, meshNetworkKey: string)The Crownstone uses encryption by default, so this library needs keys to encrypt and decrypt data.
These keys are 16 characters long like "adminKeyForCrown" or 32 characters as a hex string like "9332b7abf19b86ff48156d88c687def6".
Your keys can be obtained from the cloud. Either do thismanually, or use thecloud library.loadSettingsFromFile(path: string)As an alternative to using setSettings, you can load it from a json file. The path is relative to the script being executed. An example of this json file is:{
 "admin":  "adminKeyForCrown",
 "member": "memberKeyForHome",
 "basic":  "basicKeyForOther",
 "serviceDataKey":  "MyServiceDataKey",
 "localizationKey":  "aLocalizationKey",
 "meshApplicationKey":  "MyGoodMeshAppKey",
 "meshNetworkKey":  "MyGoodMeshNetKey",
}Searching for CrownstonesIn order to do something with a Crownstone, you need to know which Crownstones there are.
This can be done by scanning. There are basic and convenience functions to do this.async getCrownstonesByScanning(scanDuration=3)This will scan for scanDuration in seconds and return an array of the Crownstone it has found. This is an array of dictionaries that look like this:{
   "address": string,      # mac address like "f7:19:a4:ef:ea:f6"
   "setupMode": boolean,   # is this Crownstone in setup mode?
   "validated": boolean,   # if True, this Crownstone belongs to your Sphere (ie. it can be decrypted by the provided keys).
   "rssi": Float           # average of the rssi of this Crownstone. If None, there have been no valid measurements.
}This array can be directly put in the 'addressesToExclude' field of the 'getNearest..' methods.async startScanning(scanDuration=3)This will start scanning for Crownstones in a background thread. ThescanDurationdenotes how long we will scan for.
Once scanning is active,BleTopics.advertisementevents will be triggered with the advertisements of the
Crownstones that share our encryption keys or are in setup mode.async stopScanning()This will stop an active scan.async getNearestCrownstone(rssiAtLeast=-100, scanDuration=3, returnFirstAcceptable=False, addressesToExclude=[]) -> ScanData or NoneThis will search for the nearest Crownstone. It will return ANY Crownstone, not just the ones sharing our encryption keys.rssiAtLeast, you can use this to indicate a maximum distancescanDuration, the amount of time we scan (in seconds)returnFirstAcceptable, if this is True, we return on the first Crownstone in the rssiAtLeast range. If it is False, we will scan for the timeout duration and return the closest one.addressesToExclude, this is an array of either address strings (like "f7:19:a4:ef:ea:f6") or an array of dictionaries that each contain an address field (like what you get from "getCrownstonesByScanning").If anything was found, the ScanData will be returned.This datatype is defined here.async getNearestValidatedCrownstone(rssiAtLeast=-100, scanDuration=3, returnFirstAcceptable=False, addressesToExclude=[]) -> ScanData or NoneSame as getNearestCrownstone but will only search for Crownstones with the same encryption keys.
If anything was found, the ScanData will be returned.This datatype is defined here.async getNearestSetupCrownstone(rssiAtLeast=-100, scanDuration=3, returnFirstAcceptable=False, addressesToExclude=[]) -> ScanData or NoneSame as getNearestCrownstone but will only search for Crownstones in setup mode.
If anything was found, the ScanData will be returned.This datatype is defined here.ConnectingMost commands from thecontrolandstatemodules will require you to connect to a Crownstone before sending the command.async connect(address: string)This will connect to the Crownstone with the provided MAC address. You get get this address by scanning or getting the nearest Crownstone.async disconnect()This will disconnect from the Crownstone.Operation modeA fresh Crownstone starts in operation mode "setup". In this mode, it has limited functionality and does not belong to anyone. You can claim it by performing a setup, which is usually done with the smartphone app, as that also registers it at the cloud.async def getMode(self, address, scanDuration=3) -> CrownstoneOperationModeThis will scan 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 raise a CrownstoneBleException with the following types:BleError.NO_SCANS_RECEIVEDWe have not received any scans from this Crownstone, and can't say anything about it's state.async def waitForMode(self, address, requiredMode: CrownstoneOperationMode, scanDuration=3) -> CrownstoneOperationModeThis 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 raise a CrownstoneBleException with the following types:BleError.NO_SCANS_RECEIVEDWe have not received any scans from this Crownstone, and can't say anything about it's state.BleError.DIFFERENT_MODE_THAN_REQUIREDDuring thescanDurationseconds of scanning, the Crownstone was not in the required mode.async setupCrownstone(address: string, sphereId: int, crownstoneId: int, meshDeviceKey: string, ibeaconUUID: string, ibeaconMajor: uint16, ibeaconMinor: uint16)New Crownstones are in setup mode. In this mode they are open to receiving encryption keys. This method facilitates this process. No manual connection is required.address is the MAC address.sphereId is a uint8 id for this Crownstone's sphere.crownstoneId is a uint8 id for this Crownstone.meshDeviceKey is a 16 character string.ibeaconUUID is a string like "d8b094e7-569c-4bc6-8637-e11ce4221c18".ibeaconMajor is a number between 0 and 65535.ibeaconMinor is a number between 0 and 65535.Control moduleThe modules contain groups of methods. You can access them like this:importasynciofromcrownstone_bleimportCrownstoneBle# initialize the Bluetooth Coreble=CrownstoneBle()asyncdefexample():# set the switch stat eusing the control moduleawaitble.connect(address)# address is a mac address (or handle on OSX)awaitble.control.setSwitch(0)awaitble.disconnect()loop=asyncio.get_event_loop()loop.run_until_complete(example())Methods:async setSwitch(switchVal: int)You can switch the Crownstone. 0 for off, 100 for on, between 0 and 100 to dim. There are also special values to be found inSwitchValSpecial. If you want to dim, make sure dimming is enabled. You can enable this using theallowDimming()method.async commandFactoryReset()Assuming you have the encryption keys, you can use this method to put the Crownstone back into setup mode.async allowDimming(allow: bool)Enable or disable dimming on this Crownstone. Required if you want to dim withsetSwitch().async disconnect()Tell the Crownstone to disconnect from you. This can help if your Bluetooth stack does not reliably disconnect.async lockSwitch(lock: bool)Lock the switch. If locked, its switch state cannot be changed.async reset()Restart the Crownstone.State moduleThis is used to get state variables from the Crownstone. [https://github.com/crownstone/bluenet/blob/master/docs/PROTOCOL.md#state-packet-1]The modules contain groups of methods. You can access them like this:importasynciofromcrownstone_bleimportCrownstoneBle# initialize the Bluetooth Coreble=CrownstoneBle()asyncdefexample():# set the switch state using the control moduleawaitble.connect(address)switchstate=awaitble.state.getSwitchState()awaitble.disconnect()loop=asyncio.get_event_loop()loop.run_until_complete(example())async getSwitchState()Get the switch state asSwitchStateclass.async getTime()Get the time on the Crownstone as a timestamp since epoch in seconds. This has been corrected for location.Event busAPIonce(TopicName: string, functionPointer)This will subscribe for a single event. After this event, the listener will be removed automatically. It still returns a unsubscriptionId if you want to cleanup before the event occurs.subscribe(TopicName: string, functionPointer)Returns a subscription ID that can be used to unsubscribe again with the unsubscribe methodunsubscribe(subscriptionId: number)This will stop the invocation of the function you provided in the subscribe method, unsubscribing you from the event.EventsThese events are available for the BLE part of this lib:BleTopics.newDataAvailableThis is a topic to which events are posted which are unique. The same message will be repeated on the advertisement and the rawAdvertisement packets.BleTopics.rawAdvertisementThis topic will broadcast all incoming Crownstone scans, including those that do not belong to your sphere (ie. can't be decrypted with your keys).BleTopics.advertisementThis topic will broadcast all incoming Crownstone scans which belong to your sphere (ie. which can be decrypted with your keys).Data formatAll these events contain the same data format:classScanData:def__init__(self):self.address=None# this is the handle of the device that broadcast the advertisement. This is usually a MAC address, but on OSX it is a handle.self.rssi=None# the signal strength indicatorself.name=None# name of the deviceself.operationMode=None# CrownstoneOperationMode enum (SETUP, NORMAL, DFU, UNKNOWN)self.serviceUUID=None# the UUID of the scanned serviceself.deviceType=None# type of Crownstoneself.payload=None# See below.self.validated=None# Whether your provided keys could decrypt this advertisementThese fields are always filled. The payload will differ depending on what sort of data is advertised.You can see all possible types here.These payloads all have atypefieldwhich is defined here.Payloads come in these flavours:CROWNSTONE_STATECROWNSTONE_ERROREXTERNAL_STATEEXTERNAL_ERRORALTERNATIVE_STATEHUB_STATEMICROAPP_DATASETUP_STATEUsageYou can obtain the eventBus directly from the lib:fromcrownstone_bleimportBleEventBus,BleTopics# simple example function to print the data you receivedefshowNewData(data):print("received new data: ",data)# Set up event listenerssubscriptionId=BleEventBus.subscribe(BleTopics.newDataAvailable,showNewData)# unsubscribe againBleEventBus.unsubscribe(subscriptionId)Common issuesBluetooth on LinuxIf bluetooth seems stuck, try:sudo rfkill block bluetooth
sudo rfkill unblock bluetooth