Package: ruuvitag-sensor
RuuviTag Sensor Python Packageruuvitag-sensoris a Python package for communicating withRuuviTag BLE Sensorand for decoding measurement data from broadcasted BLE data.Documentation website:https://ttu.github.io/ruuvitag-sensor/RequirementsRuuviTag sensorSetupguideSupportsData Format 2, 3, 4 and 5NOTE:Data Formats 2, 3 and 4 aredeprecatedand should not be usedBluez (Linux-only)Default adapter for LinuxBluez supportsSync-methodsObservable streamsInstall guideBleakcommunication module (Windows, macOS and Linux)Default adapter for Windows and macOSBleak supportsAsync-methodsObservable streamsInstall guidePython 3.7+For Python 2.x or <3.7 support, checkinstallation instructionsfor an older versionNOTE:Version 2.0 contains method renames. When using a version prior to 2.0, check the documentation and examples fromPyPIor in GitHub, switch to the correct release tag fromswitch branches/tags.InstallationInstall the latest released version$python-mpipinstallruuvitag-sensorInstall the latest development version$python-mvenv.venv
$source.venv/bin/activate
$python-mpipinstallgit+https://github.com/ttu/ruuvitag-sensor# For development, clone this repository and install for development in editable mode$python-mpipinstall-e.[dev]Full installation guide forRaspberry PI & RaspbianUsageThe package provides 3 ways to fetch data from sensors:Synchronously with callbackAsynchronously with async/awaitObservable streams with ReactiveXRuuviTag sensors can be identified using MAC addresses. Methods return a tuple with MAC and sensor data payload.('D2:A3:6E:C8:E0:25',{'data_format':5,'humidity':47.62,'temperature':23.58,'pressure':1023.68,'acceleration':993.2331045630729,'acceleration_x':-48,'acceleration_y':-12,'acceleration_z':992,'tx_power':4,'battery':2197,'movement_counter':0,'measurement_sequence_number':88,'mac':'d2a36ec8e025','rssi':-80})1. Get sensor data synchronously with callbackget_datacalls the callback whenever a RuuviTag sensor broadcasts data. This method is the preferred way to use the library withBlueZ.fromruuvitag_sensor.ruuviimportRuuviTagSensordefhandle_data(found_data):print(f"MAC{found_data[0]}")print(f"Data{found_data[1]}")if__name__=="__main__":RuuviTagSensor.get_data(handle_data)The lineif __name__ == "__main__":is required on Windows and macOS due to the way themultiprocessinglibrary works. It is not required on Linux, but it is recommended. It is omitted from the rest of the examples below.The optional list of MACs and run flag can be passed to theget_datafunction. The callback is called only for MACs in the list and setting the run flag to false will stop execution. If the run flag is not passed, the function will execute forever.fromruuvitag_sensor.ruuviimportRuuviTagSensor,RunFlagcounter=10# RunFlag for stopping execution at desired timerun_flag=RunFlag()defhandle_data(found_data):print(f"MAC:{found_data[0]}")print(f"Data:{found_data[1]}")globalcountercounter=counter-1ifcounter<0:run_flag.running=False# List of MACs of sensors which will execute callback functionmacs=["AA:2C:6A:1E:59:3D","CC:2C:6A:1E:59:3D"]RuuviTagSensor.get_data(handle_data,macs,run_flag)2. Get sensor data asynchronouslyNOTE:Asynchronous functionality works only withBleak-adapter.get_data_asyncreturns the data whenever a RuuviTag sensor broadcasts data.get_data_asyncwill exceute until iterator is exited. This method is the preferred way to use the library withBleak.importasynciofromruuvitag_sensor.ruuviimportRuuviTagSensorasyncdefmain():asyncforfound_datainRuuviTagSensor.get_data_async():print(f"MAC:{found_data[0]}")print(f"Data:{found_data[1]}")if__name__=="__main__":asyncio.get_event_loop().run_until_complete(main())The optional list of MACs can be passed to theget_data_asyncfunction.importasynciofromruuvitag_sensor.ruuviimportRuuviTagSensormacs=["AA:2C:6A:1E:59:3D","CC:2C:6A:1E:59:3D"]asyncdefmain():# Get data only for defineded MACs. Exit after 10 found resultsdatas=[]asyncforfound_datainRuuviTagSensor.get_data_async(macs):print(f"MAC:{found_data[0]}")print(f"Data:{found_data[1]}")datas.push(found_data)iflen(datas)>10:breakif__name__=="__main__":asyncio.get_event_loop().run_until_complete(main())3. Get sensor data with observable streams (ReactiveX / RxPY)RuuviTagReactiveis a reactive wrapper and background process for RuuviTagSensorget_data. An optional MAC address list can be passed on the initializer and execution can be stopped with the stop function.fromruuvitag_sensor.ruuvi_rximportRuuviTagReactivefromreactiveximportoperatorsasopsruuvi_rx=RuuviTagReactive()# Print all notificationsruuvi_rx.get_subject().\subscribe(print)# Print only last data every 10 seconds for F4:A5:74:89:16:57ruuvi_rx.get_subject().pipe(ops.filter(lambdax:x[0]=="F4:A5:74:89:16:57"),ops.buffer_with_time(10.0)).subscribe(lambdadata:print(data[len(data)-1]))# Execute only every time when temperature changes for F4:A5:74:89:16:57ruuvi_rx.get_subject().pipe(ops.filter(lambdax:x[0]=="F4:A5:74:89:16:57"),ops.map(lambdax:x[1]["temperature"]),ops.distinct_until_changed()).subscribe(lambdax:print(f"Temperature changed:{x}"))# Close all connections and stop bluetooth communicationruuvi_rx.stop()Moresamplesand a simpleHTTP serverunder the examples directory.Check the official documentation ofReactiveXand thelist of operators.Other helper methodsGet data for specified sensors for a specific durationget_data_for_sensorsandget_data_for_sensors_asyncwill collect the latest data from sensors for a specified duration.fromruuvitag_sensor.ruuviimportRuuviTagSensor# List of MACs of sensors which data will be collected# If list is empty, data will be collected for all found sensorsmacs=["AA:2C:6A:1E:59:3D","CC:2C:6A:1E:59:3D"]# get_data_for_sensors will look data for the duration of timeout_in_sectimeout_in_sec=4data=RuuviTagSensor.get_data_for_sensors(macs,timeout_in_sec)# data = await RuuviTagSensor.get_data_for_sensors_async(macs, timeout_in_sec)# Dictionary will have latest data for each sensorprint(data["AA:2C:6A:1E:59:3D"])print(data["CC:2C:6A:1E:59:3D"])NOTE:These methods shouldn't be used for a long duration with a short timeout. Methods will start and stop a new BLE scanning process with every method call. For long-running processes, it is recommended to use theget_data- andget_data_async-method.Get data from a sensorNOTE:For a single sensor it is recommended to useRuuviTagSensor.get_dataorRuuviTagSensor.get_data_asyncmethods instead ofRuuviTag- orRuuviTagAsync-class.RuuviTagAsync-class doesn't work with macOS, due to the way how macOS returns MAC address.fromruuvitag_sensor.ruuvitagimportRuuviTagsensor=RuuviTag("AA:2C:6A:1E:59:3D")# update state from the devicestate=sensor.update()# get latest state (does not get it from the device)state=sensor.stateprint(state)Find sensorsRuuviTagSensor.find_ruuvitagsandRuuviTagSensor.find_ruuvitags_asyncmethods will execute forever and when a new RuuviTag sensor is found, it will print its MAC address and state at that moment. This function can be used with command-line applications. Logging must be enabled and set to print to the console.fromruuvitag_sensor.ruuviimportRuuviTagSensorimportruuvitag_sensor.logruuvitag_sensor.log.enable_console()RuuviTagSensor.find_ruuvitags()Using different Bluetooth deviceIf you have multiple Bluetooth devices installed, a device to be used might not be the default (Linux: hci0). The device can be passed with abt_device-parameter.fromruuvitag_sensor.ruuviimportRuuviTagSensorfromruuvitag_sensor.ruuvitagimportRuuviTagsensor=RuuviTag("F4:A5:74:89:16:57","hci1")RuuviTagSensor.find_ruuvitags("hci1")data=RuuviTagSensor.get_data_for_sensors(bt_device="hci1")RuuviTagSensor.get_data(lambdax:print(f"{x[0]}-{x[1]}"),bt_device=device))Parse datafromruuvitag_sensor.data_formatsimportDataFormatsfromruuvitag_sensor.decoderimportget_decoderfull_data="043E2A0201030157168974A51F0201060303AAFE1716AAFE10F9037275752E76692F23416A5558314D417730C3"data=full_data[26:]# convert_data returns tuple which has Data Format type and encoded data(data_format,encoded)=DataFormats.convert_data(data)sensor_data=get_decoder(data_format).decode_data(encoded)print(sensor_data)# {'temperature': 25.12, 'identifier': '0', 'humidity': 26.5, 'pressure': 992.0}RuuviTag Data FormatsExample data has data from 4 sensors with different firmware.1st is Data Format 2 (URL), the identifier is None as the sensor doesn't broadcast any identifier data2nd is Data Format 4 (URL) and it has an identifier character3rd is Data Format 3 (RAW)4th is Data Format 5 (RAW v2){'CA:F7:44:DE:EB:E1':{'data_format':2,'temperature':22.0,'humidity':28.0,'pressure':991.0,'identifier':None,'rssi':None},'F4:A5:74:89:16:57':{'data_format':4,'temperature':23.24,'humidity':29.0,'pressure':991.0,'identifier':'0','rssi':None},'A3:GE:2D:91:A4:1F':{'data_format':3,'battery':2899,'pressure':1027.66,'humidity':20.5,'acceleration':63818.215675463696,'acceleration_x':200.34,'acceleration_y':0.512,'acceleration_z':-200.42,'temperature':26.3,'rssi':None},'CB:B8:33:4C:88:4F':{'data_format':5,'battery':2.995,'pressure':1000.43,'mac':'cbb8334c884f','measurement_sequence_number':2467,'acceleration_z':1028,'acceleration':1028.0389097694697,'temperature':22.14,'acceleration_y':-8,'acceleration_x':4,'humidity':53.97,'tx_power':4,'movement_counter':70,'rssi':-65}}Note on Data Format 2 and 4There is no reason to use Data Format 2 or 4.The original reason to use the URL-encoded data was to useGoogle's Nearbynotifications to let users view tags without the need to install any app. SinceGoogle's Nearbyhas been discontinued, there isn't any benefit in using the Eddystone format anymore.Logging and printing to the consoleLogging can be enabled by importingruuvitag_sensor.log. Console print can be enabled by callingruuvitag_sensor.log.enable_console(). The command line application has console logging enabled by default.fromruuvitag_sensor.ruuviimportRuuviTagSensorimportruuvitag_sensor.logruuvitag_sensor.log.enable_console()data=RuuviTagSensor.get_data_for_sensors()print(data)To enable debug logging to console, set log-level toDEBUG.importloggingimportruuvitag_sensor.logfromruuvitag_sensor.logimportlogfromruuvitag_sensor.ruuviimportRuuviTagSensorruuvitag_sensor.log.enable_console()log.setLevel(logging.DEBUG)forhandlerinlog.handlers:handler.setLevel(logging.DEBUG)data=RuuviTagSensor.get_data_for_sensors()print(data)Log all events to log-fileBy default only errors are logged toruuvitag_sensor.log-file. The level can be changed by changing FileHandler's log level.importloggingfromruuvitag_sensor.logimportlogfromruuvitag_sensor.ruuviimportRuuviTagSensorlog.setLevel(logging.DEBUG)forhandlerinlog.handlers:ifisinstance(handler,logging.FileHandler):handler.setLevel(logging.DEBUG)data=RuuviTagSensor.get_data_for_sensors()A custom event handler for a specific log eventIf custom functionality is required when a specific event happens, e.g. exit when a specific sensor is blacklisted, logging event handlers can be utilized for this functionality.fromloggingimportStreamHandlerfromruuvitag_sensor.logimportlogfromruuvitag_sensor.ruuviimportRuuviTagSensorclassExitHandler(StreamHandler):defemit(self,record):if(record.levelname!="DEBUG"):returnmsg=self.format(record)if"Blacklisting MAC F4:A5:74:89:16:57E"inmsg:exit(1)exit_handler=ExitHandler()log.addHandler(exit_handler)data=RuuviTagSensor.get_data_for_sensors()Command line application$ python ruuvitag_sensor -h

usage: ruuvitag_sensor [-h] [-g MAC_ADDRESS] [-d BT_DEVICE] [-f] [-l] [-s] [--version]

optional arguments:
  -h, --help            show this help message and exit
  -g MAC_ADDRESS, --get MAC_ADDRESS
                        Get data
  -d BT_DEVICE, --device BT_DEVICE
                        Set Bluetooth device id (default hci0)
  -f, --find            Find broadcasting RuuviTags
  -l, --latest          Get latest data for found RuuviTags
  -s, --stream          Stream broadcasts from all RuuviTags
  --version             show program's version number and exitBLE Communication modulesBlueZBlueZ works only onLinux. When using BlueZ, Windows and macOS support is only for testing with hard-coded data and for data decoding.BlueZ tools requiresuperuserrights.Install BlueZ.$sudoapt-getinstallbluezbluez-hcidumpruuvitag-sensorpackage uses internallyhciconfig,hcitoolandhcidump. These tools are deprecated. In case tools are missing, an older version of BlueZ is required (Issue)BlueZ limitationsruuvitag-sensorpackage uses BlueZ to listen to broadcasted BL information (useshciconf,hcitool,hcidump). Implementation does not handle well all unexpected errors or changes, e.g. when the adapter is busy, rebooted or powered down.In case of errors, the application tries to exit immediately, so it can be automatically restarted.BleakOn Windows and macOS Bleak is installed and used automatically withruuvitag-sensorpackage.On Linux install it manually from PyPI and enable it withRUUVI_BLE_ADAPTERenvironment variable.$python-mpipinstallbleakAdd environment variable RUUVI_BLE_ADAPTER with value Bleak. E.g.$exportRUUVI_BLE_ADAPTER="bleak"Or useos.environ. NOTE: this must be set before importingruuvitag_sensor.importosos.environ["RUUVI_BLE_ADAPTER"]="bleak"Bleak supports only async methods.importasynciofromruuvitag_sensor.ruuviimportRuuviTagSensorasyncdefmain():asyncfordatainRuuviTagSensor.get_data_async():print(data)if__name__=="__main__":asyncio.get_event_loop().run_until_complete(main())Checkget_async_bleakand other async examples fromexamplesdirectory.Bleak dummy BLE dataBleak-adapter has a development-time generator for dummy data, which can be useful during the development, if no sensors are available. SetRUUVI_BLE_ADAPTERenvironment variable tobleak_dev.BlesonCurrent state and known bugs inissue #78.Bleson works with Linux, macOS and partially with Windows.Bleson is not installed automatically withruuvitag-sensorpackage. Install it manually from GitHub.$pipinstallgit+https://github.com/TheCellule/python-blesonAdd environment variableRUUVI_BLE_ADAPTERwith valuebleson. E.g.$exportRUUVI_BLE_ADAPTER="bleson"NOTE:On macOS, only Data Format 5 works, as macOS doesn't advertise MAC address and only DF5 has MAC in sensor payload.RuuviTag-class doesn't work with macOS.NOTE:On Windows, Bleson requiresPython 3.6. Unfortunately on Windows, Bleson doesn't send any payload for the advertised package, so it is still unusable.Python 2.x and 3.6 and belowLast version ofruuvitag-sensorwith Python 2.x and <3.7 support is1.2.1.Branch/Tag / commit$gitcheckoutrelease/1.2.1Install from PyPI$python-mpipinstallruuvitag-sensor==1.2.1ExamplesExamples are inexamplesdirectory, e.g.Keep track of each sensor's current status and send updated data to the server.Syncandasyncversion.Send found sensor data to InfluxDB.Reactiveandnon-reactiveversion. The naming convention of sent data matchesRuuviCollector library.Simple HTTP Server for serving found sensor data.Flask,aiohttpandaiohttp with ReactiveX.ChangelogChangelogDeveloper notesNotes for developerswho are interested in developingruuvitag-sensorpackage or are interested in its internal functionality.ContributingPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.LicenseLicensed under theMITLicense.