ruuvitag-sensor


Nameruuvitag-sensor JSON
Version 2.3.1 PyPI version JSON
download
home_page
SummaryFind RuuviTag sensors and get decoded data from selected sensors
upload_time2024-03-10 15:30:33
maintainer
docs_urlNone
author
requires_python>=3.7
licenseMIT License Copyright (c) 2016 Tomi Tuhkanen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords ruuvitag ble
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            RuuviTag Sensor Python Package
---------------------------------

[![Build Status](https://github.com/ttu/ruuvitag-sensor/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/ttu/ruuvitag-sensor/actions/workflows/build.yml)
[![License](https://img.shields.io/pypi/l/ruuvitag-sensor.svg)](https://pypi.python.org/pypi/ruuvitag-sensor/)
[![PyPI version](https://img.shields.io/pypi/v/ruuvitag-sensor.svg)](https://pypi.python.org/pypi/ruuvitag_-sensor)
[![PyPI downloads](https://img.shields.io/pypi/dm/ruuvitag-sensor.svg)](https://pypistats.org/packages/ruuvitag-sensor)
[![Python versions](https://img.shields.io/badge/python-3.7+-blue.svg)](https://pypi.python.org/pypi/ruuvitag-sensor/)

`ruuvitag-sensor` is a Python package for communicating with [RuuviTag BLE Sensor](https://ruuvi.com/) and for decoding measurement data from broadcasted BLE data.

**Documentation website:** [https://ttu.github.io/ruuvitag-sensor/](https://ttu.github.io/ruuvitag-sensor/)

## Requirements

* RuuviTag sensor
    * Setup [guide](https://ruuvi.com/quick-start/)
    * Supports [Data Format 2, 3, 4 and 5](https://docs.ruuvi.com/)
      * __NOTE:__ Data Formats 2, 3 and 4 are _deprecated_ and should not be used
* [Bleak](https://github.com/hbldh/bleak) communication module (Windows, macOS and Linux)
    * Default adapter for Windows and macOS
    * Bleak supports
      * [Async-methods](#usage)
      * [Observable streams](#usage)
    * [Install guide](#Bleak)
* Bluez (Linux-only)
    * Default adapter for Linux
    * Bluez supports
      * [Sync-methods](#usage)
      * [Observable streams](#usage)
    * [Install guide](#BlueZ)
    * __NOTE:__ The BlueZ-adapter implementation uses deprecated BlueZ tools that are no longer supported.
      * Even though BlueZ is still the default adapter, it is recommended to use the Bleak-communication adapter with Linux. Bleak will be the default adapter for Linux in the next major release.
      * Bleson-adapter supports sync-methods, but please be aware that it is not fully supported due to the alpha release status of the Bleson communication module. See [Bleson](#Bleson) for more information.
* Python 3.7+
    * For Python 2.x or <3.7 support, check [installation instructions](#python-2x-and-36-and-below) for an older version

__NOTE:__ Version 2.0 contains method renames. When using a version prior to 2.0, check the documentation and examples from [PyPI](https://pypi.org/project/ruuvitag-sensor/) or in GitHub, switch to the correct release tag from _switch branches/tags_.

## Installation

Install the latest released version
```sh
$ python -m pip install ruuvitag-sensor
```

Install the latest development version
```sh
$ python -m venv .venv
$ source .venv/bin/activate
$ python -m pip install git+https://github.com/ttu/ruuvitag-sensor

# For development, clone this repository and install for development in editable mode
$ python -m pip install -e .[dev]
```

Full installation guide for [Raspberry PI & Raspbian](https://github.com/ttu/ruuvitag-sensor/blob/master/install_guide_pi.md)

## Usage

The package provides 3 ways to fetch data from sensors:

1. Asynchronously with async/await
2. Synchronously with callback
3. Observable streams with ReactiveX

RuuviTag sensors can be identified using MAC addresses. Methods return a tuple with MAC and sensor data payload.

```py
('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 asynchronously with async/await

__NOTE:__ Asynchronous functionality works only with `Bleak`-adapter.

`get_data_async` returns the data whenever a RuuviTag sensor broadcasts data. `get_data_async` will execute until iterator is exited. This method is the preferred way to use the library with _Bleak_.

```py
import asyncio
from ruuvitag_sensor.ruuvi import RuuviTagSensor


async def main():
    async for found_data in RuuviTagSensor.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 the `get_data_async` function.

```py
import asyncio
from ruuvitag_sensor.ruuvi import RuuviTagSensor

macs = ["AA:2C:6A:1E:59:3D", "CC:2C:6A:1E:59:3D"]


async def main():
    # Get data only for defineded MACs. Exit after 10 found results
    datas = []
    async for found_data in RuuviTagSensor.get_data_async(macs):
        print(f"MAC: {found_data[0]}")
        print(f"Data: {found_data[1]}")
        datas.push(found_data)
        if len(datas) > 10:
            break


if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())
```

The line `if __name__ == "__main__":` is required on Windows and macOS due to the way the `multiprocessing` library works. It is not required on Linux, but it is recommended. It is omitted from the rest of the examples below.

### 2. Get sensor data synchronously with callback

__NOTE:__ Asynchronous functionality works only with `BlueZ`-adapter.

`get_data` calls the callback whenever a RuuviTag sensor broadcasts data. This method is the preferred way to use the library with _BlueZ_.

```python
from ruuvitag_sensor.ruuvi import RuuviTagSensor


def handle_data(found_data):
    print(f"MAC {found_data[0]}")
    print(f"Data {found_data[1]}")


if __name__ == "__main__":
    RuuviTagSensor.get_data(handle_data)
```

The optional list of MACs and run flag can be passed to the `get_data` function. 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.

```python
from ruuvitag_sensor.ruuvi import RuuviTagSensor, RunFlag

counter = 10
# RunFlag for stopping execution at desired time
run_flag = RunFlag()


def handle_data(found_data):
    print(f"MAC: {found_data[0]}")
    print(f"Data: {found_data[1]}")

    global counter
    counter = counter - 1
    if counter < 0:
        run_flag.running = False


# List of MACs of sensors which will execute callback function
macs = ["AA:2C:6A:1E:59:3D", "CC:2C:6A:1E:59:3D"]

RuuviTagSensor.get_data(handle_data, macs, run_flag)
```

### 3. Get sensor data with observable streams (ReactiveX / RxPY)

`RuuviTagReactive` is a reactive wrapper and background process for RuuviTagSensor `get_data`. An optional MAC address list can be passed on the initializer and execution can be stopped with the stop function.

```python
from ruuvitag_sensor.ruuvi_rx import RuuviTagReactive
from reactivex import operators as ops

ruuvi_rx = RuuviTagReactive()

# Print all notifications
ruuvi_rx.get_subject().\
    subscribe(print)

# Print only last data every 10 seconds for F4:A5:74:89:16:57
ruuvi_rx.get_subject().pipe(
      ops.filter(lambda x: x[0] == "F4:A5:74:89:16:57"),
      ops.buffer_with_time(10.0)
    ).subscribe(lambda data: print(data[len(data) - 1]))

# Execute only every time when temperature changes for F4:A5:74:89:16:57
ruuvi_rx.get_subject().pipe(
      ops.filter(lambda x: x[0] == "F4:A5:74:89:16:57"),
      ops.map(lambda x: x[1]["temperature"]),
      ops.distinct_until_changed()
    ).subscribe(lambda x: print(f"Temperature changed: {x}"))

# Close all connections and stop Bluetooth communication
ruuvi_rx.stop()
```

More [samples](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/reactive_extensions.py) and a simple [HTTP server](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio_rx.py) under the examples directory.

Check the official documentation of [ReactiveX](https://rxpy.readthedocs.io/en/latest/index.html) and the [list of operators](https://rxpy.readthedocs.io/en/latest/operators.html).

### Other helper methods

#### Get data for specified sensors for a specific duration

`get_data_for_sensors` and `get_data_for_sensors_async` will collect the latest data from sensors for a specified duration.

```python
from ruuvitag_sensor.ruuvi import RuuviTagSensor

# List of MACs of sensors which data will be collected
# If list is empty, data will be collected for all found sensors
macs = ["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_sec
timeout_in_sec = 4

data = 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 sensor
print(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 the `get_data`- and `get_data_async`-method.

#### Get data from a sensor

__NOTE:__ For a single sensor it is recommended to use `RuuviTagSensor.get_data` or `RuuviTagSensor.get_data_async` methods instead of `RuuviTag`- or `RuuviTagAsync`-class. `RuuviTagAsync`-class doesn't work with macOS, due to the way how macOS returns MAC address.

```python
from ruuvitag_sensor.ruuvitag import RuuviTag

sensor = RuuviTag("AA:2C:6A:1E:59:3D")

# update state from the device
state = sensor.update()

# get latest state (does not get it from the device)
state = sensor.state

print(state)
```

#### Find sensors

`RuuviTagSensor.find_ruuvitags` and `RuuviTagSensor.find_ruuvitags_async` methods 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.

```python
from ruuvitag_sensor.ruuvi import RuuviTagSensor
import ruuvitag_sensor.log

ruuvitag_sensor.log.enable_console()

RuuviTagSensor.find_ruuvitags()
```

### Using different Bluetooth device

If you have multiple Bluetooth devices installed, a device to be used might not be the default (Linux: `hci0`). The device can be passed with a `bt_device`-parameter.

```python
from ruuvitag_sensor.ruuvi import RuuviTagSensor
from ruuvitag_sensor.ruuvitag import RuuviTag

sensor = RuuviTag("F4:A5:74:89:16:57", "hci1")

RuuviTagSensor.find_ruuvitags("hci1")

data = RuuviTagSensor.get_data_for_sensors(bt_device="hci1")

RuuviTagSensor.get_data(lambda x: print(f"{x[0]} - {x[1]}"), bt_device=device))
```

### Parse data

```python
from ruuvitag_sensor.data_formats import DataFormats
from ruuvitag_sensor.decoder import get_decoder

full_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 Formats

Example 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 data
* 2nd is Data Format 4 (URL) and it has an identifier character
* 3rd is Data Format 3 (RAW)
* 4th is Data Format 5 (RAW v2)

```python
{
'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 4

There is no reason to use Data Format 2 or 4.

The original reason to use the URL-encoded data was to use _Google's Nearby_ notifications to let users view tags without the need to install any app. Since _Google's Nearby_ has been discontinued, there isn't any benefit in using the Eddystone format anymore.

## Logging and printing to the console

Logging can be enabled by importing `ruuvitag_sensor.log`. Console print can be enabled by calling `ruuvitag_sensor.log.enable_console()`. The command line application has console logging enabled by default.

```py
from ruuvitag_sensor.ruuvi import RuuviTagSensor
import ruuvitag_sensor.log

ruuvitag_sensor.log.enable_console()

data = RuuviTagSensor.get_data_for_sensors()

print(data)
```

To enable debug logging to console, set log-level to `DEBUG`.

```py
import logging
import ruuvitag_sensor.log
from ruuvitag_sensor.log import log
from ruuvitag_sensor.ruuvi import RuuviTagSensor

ruuvitag_sensor.log.enable_console()

log.setLevel(logging.DEBUG)

for handler in log.handlers:
    handler.setLevel(logging.DEBUG)

data = RuuviTagSensor.get_data_for_sensors()

print(data)
```

### Log all events to log-file

By default only errors are logged to `ruuvitag_sensor.log`-file. The level can be changed by changing FileHandler's log level.

```py
import logging
from ruuvitag_sensor.log import log
from ruuvitag_sensor.ruuvi import RuuviTagSensor

log.setLevel(logging.DEBUG)

for handler in log.handlers:
    if isinstance(handler, logging.FileHandler):
        handler.setLevel(logging.DEBUG)

data = RuuviTagSensor.get_data_for_sensors()
```

### A custom event handler for a specific log event

If 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.

```py
from logging import StreamHandler
from ruuvitag_sensor.log import log
from ruuvitag_sensor.ruuvi import RuuviTagSensor


class ExitHandler(StreamHandler):

    def emit(self, record):
        if (record.levelname != "DEBUG"):
            return
        msg = self.format(record)
        if "Blacklisting MAC F4:A5:74:89:16:57E" in msg:
            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 exit
```

## BLE Communication modules

### BlueZ

BlueZ works only on __Linux__. When using BlueZ, Windows and macOS support is only for testing with hard-coded data and for data decoding.

BlueZ tools require __superuser__ rights.

Install BlueZ.

```sh
$ sudo apt-get install bluez bluez-hcidump
```

`ruuvitag-sensor` package uses internally _hciconfig_, _hcitool_ and _hcidump_. These tools are deprecated. In case tools are missing, an older version of BlueZ is required ([Issue](https://github.com/ttu/ruuvitag-sensor/issues/31))

If you wish to test the library on Windows or macOS, enable it with `RUUVI_BLE_ADAPTER` environment variable.

```sh
$ export RUUVI_BLE_ADAPTER="bluez"
```

And install ptyprocess.

```sh
python -m pip install ptyprocess
```

#### BlueZ limitations

`ruuvitag-sensor` package uses BlueZ to listen to broadcasted BL information (uses _hciconf_, _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.

### Bleak

On Windows and macOS Bleak is installed and used automatically with `ruuvitag-sensor` package. 

On Linux install it manually from PyPI and enable it with `RUUVI_BLE_ADAPTER` environment variable.

```sh
$ python -m pip install bleak
```

Add environment variable RUUVI_BLE_ADAPTER with value Bleak. E.g.

```sh
$ export RUUVI_BLE_ADAPTER="bleak"
```

Or use `os.environ`. __NOTE:__ this must be set before importing `ruuvitag_sensor`.

```py
import os

os.environ["RUUVI_BLE_ADAPTER"] = "bleak"
```

Bleak supports only async methods.

```py
import asyncio
from ruuvitag_sensor.ruuvi import RuuviTagSensor


async def main():
    async for data in RuuviTagSensor.get_data_async():
        print(data)


if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())
```

Check [get_async_bleak](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/get_async_bleak.py) and other async examples from [examples](https://github.com/ttu/ruuvitag-sensor/tree/master/examples) directory.

#### Bleak dummy BLE data

Bleak-adapter has a development-time generator for dummy data, which can be useful during the development, if no sensors are available. Set `RUUVI_BLE_ADAPTER` environment variable to `bleak_dev`.

### Bleson

Current state and known bugs in [issue #78](https://github.com/ttu/ruuvitag-sensor/issues/78).

[Bleson](https://github.com/TheCellule/python-bleson) works with Linux, macOS and partially with Windows.

Bleson is not installed automatically with `ruuvitag-sensor` package. Install it manually from GitHub.

```sh
$ python -m pip install git+https://github.com/TheCellule/python-bleson
```

Add environment variable `RUUVI_BLE_ADAPTER` with value `bleson`. E.g.

```sh
$ export RUUVI_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 requires _Python 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 below

Last version of `ruuvitag-sensor` with Python 2.x and <3.7 support is [1.2.1](https://pypi.org/project/ruuvitag-sensor/1.2.1/).

[Branch](https://github.com/ttu/ruuvitag-sensor/tree/release/1.2.1) / [Tag / commit](https://github.com/ttu/ruuvitag-sensor/commit/12ca3cfcb7fbed28477bb34f3bffd3eee0f9888d)

```sh
$ git checkout release/1.2.1
```

Install from PyPI
```sh
$ python -m pip install ruuvitag-sensor==1.2.1
```

## Examples

Examples are in [examples](https://github.com/ttu/ruuvitag-sensor/tree/master/examples) directory, e.g.

* Keep track of each sensor's current status and send updated data to the server. [Sync](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/send_updated_sync.py) and [async](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/send_updated_async.py) version.
* Send found sensor data to InfluxDB. [Reactive](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/post_to_influxdb_rx.py) and [non-reactive](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/post_to_influxdb.py) version. The naming convention of sent data matches [RuuviCollector library](https://github.com/scrin/ruuvicollector).
* Simple HTTP Server for serving found sensor data. [Flask](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server.py), [aiohttp](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio.py) and [aiohttp with ReactiveX](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio_rx.py).

## Changelog

[Changelog](https://github.com/ttu/ruuvitag-sensor/blob/master/CHANGELOG.md)

## Developer notes

[Notes for developers](https://github.com/ttu/ruuvitag-sensor/blob/master/developer_notes.md) who are interested in developing `ruuvitag-sensor` package or are interested in its internal functionality.

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

## License

Licensed under the [MIT](https://github.com/ttu/ruuvitag-sensor/blob/master/LICENSE) License.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "ruuvitag-sensor",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "RuuviTag BLE",
    "author": "",
    "author_email": "Tomi Tuhkanen <tomi.tuhkanen@iki.fi>",
    "download_url": "https://files.pythonhosted.org/packages/d8/05/8fa451426802ed1a7403ea10ea3cff713140e291a6ddf77a6a70155dad2a/ruuvitag_sensor-2.3.1.tar.gz",
    "platform": null,
    "description": "RuuviTag Sensor Python Package\n---------------------------------\n\n[![Build Status](https://github.com/ttu/ruuvitag-sensor/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/ttu/ruuvitag-sensor/actions/workflows/build.yml)\n[![License](https://img.shields.io/pypi/l/ruuvitag-sensor.svg)](https://pypi.python.org/pypi/ruuvitag-sensor/)\n[![PyPI version](https://img.shields.io/pypi/v/ruuvitag-sensor.svg)](https://pypi.python.org/pypi/ruuvitag_-sensor)\n[![PyPI downloads](https://img.shields.io/pypi/dm/ruuvitag-sensor.svg)](https://pypistats.org/packages/ruuvitag-sensor)\n[![Python versions](https://img.shields.io/badge/python-3.7+-blue.svg)](https://pypi.python.org/pypi/ruuvitag-sensor/)\n\n`ruuvitag-sensor` is a Python package for communicating with [RuuviTag BLE Sensor](https://ruuvi.com/) and for decoding measurement data from broadcasted BLE data.\n\n**Documentation website:** [https://ttu.github.io/ruuvitag-sensor/](https://ttu.github.io/ruuvitag-sensor/)\n\n## Requirements\n\n* RuuviTag sensor\n    * Setup [guide](https://ruuvi.com/quick-start/)\n    * Supports [Data Format 2, 3, 4 and 5](https://docs.ruuvi.com/)\n      * __NOTE:__ Data Formats 2, 3 and 4 are _deprecated_ and should not be used\n* [Bleak](https://github.com/hbldh/bleak) communication module (Windows, macOS and Linux)\n    * Default adapter for Windows and macOS\n    * Bleak supports\n      * [Async-methods](#usage)\n      * [Observable streams](#usage)\n    * [Install guide](#Bleak)\n* Bluez (Linux-only)\n    * Default adapter for Linux\n    * Bluez supports\n      * [Sync-methods](#usage)\n      * [Observable streams](#usage)\n    * [Install guide](#BlueZ)\n    * __NOTE:__ The BlueZ-adapter implementation uses deprecated BlueZ tools that are no longer supported.\n      * Even though BlueZ is still the default adapter, it is recommended to use the Bleak-communication adapter with Linux. Bleak will be the default adapter for Linux in the next major release.\n      * Bleson-adapter supports sync-methods, but please be aware that it is not fully supported due to the alpha release status of the Bleson communication module. See [Bleson](#Bleson) for more information.\n* Python 3.7+\n    * For Python 2.x or <3.7 support, check [installation instructions](#python-2x-and-36-and-below) for an older version\n\n__NOTE:__ Version 2.0 contains method renames. When using a version prior to 2.0, check the documentation and examples from [PyPI](https://pypi.org/project/ruuvitag-sensor/) or in GitHub, switch to the correct release tag from _switch branches/tags_.\n\n## Installation\n\nInstall the latest released version\n```sh\n$ python -m pip install ruuvitag-sensor\n```\n\nInstall the latest development version\n```sh\n$ python -m venv .venv\n$ source .venv/bin/activate\n$ python -m pip install git+https://github.com/ttu/ruuvitag-sensor\n\n# For development, clone this repository and install for development in editable mode\n$ python -m pip install -e .[dev]\n```\n\nFull installation guide for [Raspberry PI & Raspbian](https://github.com/ttu/ruuvitag-sensor/blob/master/install_guide_pi.md)\n\n## Usage\n\nThe package provides 3 ways to fetch data from sensors:\n\n1. Asynchronously with async/await\n2. Synchronously with callback\n3. Observable streams with ReactiveX\n\nRuuviTag sensors can be identified using MAC addresses. Methods return a tuple with MAC and sensor data payload.\n\n```py\n('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})\n```\n\n### 1. Get sensor data asynchronously with async/await\n\n__NOTE:__ Asynchronous functionality works only with `Bleak`-adapter.\n\n`get_data_async` returns the data whenever a RuuviTag sensor broadcasts data. `get_data_async` will execute until iterator is exited. This method is the preferred way to use the library with _Bleak_.\n\n```py\nimport asyncio\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\n\nasync def main():\n    async for found_data in RuuviTagSensor.get_data_async():\n        print(f\"MAC: {found_data[0]}\")\n        print(f\"Data: {found_data[1]}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.get_event_loop().run_until_complete(main())\n```\n\nThe optional list of MACs can be passed to the `get_data_async` function.\n\n```py\nimport asyncio\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\nmacs = [\"AA:2C:6A:1E:59:3D\", \"CC:2C:6A:1E:59:3D\"]\n\n\nasync def main():\n    # Get data only for defineded MACs. Exit after 10 found results\n    datas = []\n    async for found_data in RuuviTagSensor.get_data_async(macs):\n        print(f\"MAC: {found_data[0]}\")\n        print(f\"Data: {found_data[1]}\")\n        datas.push(found_data)\n        if len(datas) > 10:\n            break\n\n\nif __name__ == \"__main__\":\n    asyncio.get_event_loop().run_until_complete(main())\n```\n\nThe line `if __name__ == \"__main__\":` is required on Windows and macOS due to the way the `multiprocessing` library works. It is not required on Linux, but it is recommended. It is omitted from the rest of the examples below.\n\n### 2. Get sensor data synchronously with callback\n\n__NOTE:__ Asynchronous functionality works only with `BlueZ`-adapter.\n\n`get_data` calls the callback whenever a RuuviTag sensor broadcasts data. This method is the preferred way to use the library with _BlueZ_.\n\n```python\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\n\ndef handle_data(found_data):\n    print(f\"MAC {found_data[0]}\")\n    print(f\"Data {found_data[1]}\")\n\n\nif __name__ == \"__main__\":\n    RuuviTagSensor.get_data(handle_data)\n```\n\nThe optional list of MACs and run flag can be passed to the `get_data` function. 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.\n\n```python\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor, RunFlag\n\ncounter = 10\n# RunFlag for stopping execution at desired time\nrun_flag = RunFlag()\n\n\ndef handle_data(found_data):\n    print(f\"MAC: {found_data[0]}\")\n    print(f\"Data: {found_data[1]}\")\n\n    global counter\n    counter = counter - 1\n    if counter < 0:\n        run_flag.running = False\n\n\n# List of MACs of sensors which will execute callback function\nmacs = [\"AA:2C:6A:1E:59:3D\", \"CC:2C:6A:1E:59:3D\"]\n\nRuuviTagSensor.get_data(handle_data, macs, run_flag)\n```\n\n### 3. Get sensor data with observable streams (ReactiveX / RxPY)\n\n`RuuviTagReactive` is a reactive wrapper and background process for RuuviTagSensor `get_data`. An optional MAC address list can be passed on the initializer and execution can be stopped with the stop function.\n\n```python\nfrom ruuvitag_sensor.ruuvi_rx import RuuviTagReactive\nfrom reactivex import operators as ops\n\nruuvi_rx = RuuviTagReactive()\n\n# Print all notifications\nruuvi_rx.get_subject().\\\n    subscribe(print)\n\n# Print only last data every 10 seconds for F4:A5:74:89:16:57\nruuvi_rx.get_subject().pipe(\n      ops.filter(lambda x: x[0] == \"F4:A5:74:89:16:57\"),\n      ops.buffer_with_time(10.0)\n    ).subscribe(lambda data: print(data[len(data) - 1]))\n\n# Execute only every time when temperature changes for F4:A5:74:89:16:57\nruuvi_rx.get_subject().pipe(\n      ops.filter(lambda x: x[0] == \"F4:A5:74:89:16:57\"),\n      ops.map(lambda x: x[1][\"temperature\"]),\n      ops.distinct_until_changed()\n    ).subscribe(lambda x: print(f\"Temperature changed: {x}\"))\n\n# Close all connections and stop Bluetooth communication\nruuvi_rx.stop()\n```\n\nMore [samples](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/reactive_extensions.py) and a simple [HTTP server](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio_rx.py) under the examples directory.\n\nCheck the official documentation of [ReactiveX](https://rxpy.readthedocs.io/en/latest/index.html) and the [list of operators](https://rxpy.readthedocs.io/en/latest/operators.html).\n\n### Other helper methods\n\n#### Get data for specified sensors for a specific duration\n\n`get_data_for_sensors` and `get_data_for_sensors_async` will collect the latest data from sensors for a specified duration.\n\n```python\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\n# List of MACs of sensors which data will be collected\n# If list is empty, data will be collected for all found sensors\nmacs = [\"AA:2C:6A:1E:59:3D\", \"CC:2C:6A:1E:59:3D\"]\n# get_data_for_sensors will look data for the duration of timeout_in_sec\ntimeout_in_sec = 4\n\ndata = RuuviTagSensor.get_data_for_sensors(macs, timeout_in_sec)\n# data = await RuuviTagSensor.get_data_for_sensors_async(macs, timeout_in_sec)\n\n# Dictionary will have latest data for each sensor\nprint(data[\"AA:2C:6A:1E:59:3D\"])\nprint(data[\"CC:2C:6A:1E:59:3D\"])\n```\n\n__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 the `get_data`- and `get_data_async`-method.\n\n#### Get data from a sensor\n\n__NOTE:__ For a single sensor it is recommended to use `RuuviTagSensor.get_data` or `RuuviTagSensor.get_data_async` methods instead of `RuuviTag`- or `RuuviTagAsync`-class. `RuuviTagAsync`-class doesn't work with macOS, due to the way how macOS returns MAC address.\n\n```python\nfrom ruuvitag_sensor.ruuvitag import RuuviTag\n\nsensor = RuuviTag(\"AA:2C:6A:1E:59:3D\")\n\n# update state from the device\nstate = sensor.update()\n\n# get latest state (does not get it from the device)\nstate = sensor.state\n\nprint(state)\n```\n\n#### Find sensors\n\n`RuuviTagSensor.find_ruuvitags` and `RuuviTagSensor.find_ruuvitags_async` methods 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.\n\n```python\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\nimport ruuvitag_sensor.log\n\nruuvitag_sensor.log.enable_console()\n\nRuuviTagSensor.find_ruuvitags()\n```\n\n### Using different Bluetooth device\n\nIf you have multiple Bluetooth devices installed, a device to be used might not be the default (Linux: `hci0`). The device can be passed with a `bt_device`-parameter.\n\n```python\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\nfrom ruuvitag_sensor.ruuvitag import RuuviTag\n\nsensor = RuuviTag(\"F4:A5:74:89:16:57\", \"hci1\")\n\nRuuviTagSensor.find_ruuvitags(\"hci1\")\n\ndata = RuuviTagSensor.get_data_for_sensors(bt_device=\"hci1\")\n\nRuuviTagSensor.get_data(lambda x: print(f\"{x[0]} - {x[1]}\"), bt_device=device))\n```\n\n### Parse data\n\n```python\nfrom ruuvitag_sensor.data_formats import DataFormats\nfrom ruuvitag_sensor.decoder import get_decoder\n\nfull_data = \"043E2A0201030157168974A51F0201060303AAFE1716AAFE10F9037275752E76692F23416A5558314D417730C3\"\ndata = full_data[26:]\n\n# convert_data returns tuple which has Data Format type and encoded data\n(data_format, encoded) = DataFormats.convert_data(data)\n\nsensor_data = get_decoder(data_format).decode_data(encoded)\n\nprint(sensor_data)\n# {'temperature': 25.12, 'identifier': '0', 'humidity': 26.5, 'pressure': 992.0}\n```\n\n## RuuviTag Data Formats\n\nExample data has data from 4 sensors with different firmware.\n* 1st is Data Format 2 (URL), the identifier is None as the sensor doesn't broadcast any identifier data\n* 2nd is Data Format 4 (URL) and it has an identifier character\n* 3rd is Data Format 3 (RAW)\n* 4th is Data Format 5 (RAW v2)\n\n```python\n{\n'CA:F7:44:DE:EB:E1': { 'data_format': 2, 'temperature': 22.0, 'humidity': 28.0, 'pressure': 991.0, 'identifier': None, 'rssi': None },\n'F4:A5:74:89:16:57': { 'data_format': 4, 'temperature': 23.24, 'humidity': 29.0, 'pressure': 991.0, 'identifier': '0', 'rssi': None },\n'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 },\n'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 }\n}\n```\n\n### Note on Data Format 2 and 4\n\nThere is no reason to use Data Format 2 or 4.\n\nThe original reason to use the URL-encoded data was to use _Google's Nearby_ notifications to let users view tags without the need to install any app. Since _Google's Nearby_ has been discontinued, there isn't any benefit in using the Eddystone format anymore.\n\n## Logging and printing to the console\n\nLogging can be enabled by importing `ruuvitag_sensor.log`. Console print can be enabled by calling `ruuvitag_sensor.log.enable_console()`. The command line application has console logging enabled by default.\n\n```py\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\nimport ruuvitag_sensor.log\n\nruuvitag_sensor.log.enable_console()\n\ndata = RuuviTagSensor.get_data_for_sensors()\n\nprint(data)\n```\n\nTo enable debug logging to console, set log-level to `DEBUG`.\n\n```py\nimport logging\nimport ruuvitag_sensor.log\nfrom ruuvitag_sensor.log import log\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\nruuvitag_sensor.log.enable_console()\n\nlog.setLevel(logging.DEBUG)\n\nfor handler in log.handlers:\n    handler.setLevel(logging.DEBUG)\n\ndata = RuuviTagSensor.get_data_for_sensors()\n\nprint(data)\n```\n\n### Log all events to log-file\n\nBy default only errors are logged to `ruuvitag_sensor.log`-file. The level can be changed by changing FileHandler's log level.\n\n```py\nimport logging\nfrom ruuvitag_sensor.log import log\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\nlog.setLevel(logging.DEBUG)\n\nfor handler in log.handlers:\n    if isinstance(handler, logging.FileHandler):\n        handler.setLevel(logging.DEBUG)\n\ndata = RuuviTagSensor.get_data_for_sensors()\n```\n\n### A custom event handler for a specific log event\n\nIf 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.\n\n```py\nfrom logging import StreamHandler\nfrom ruuvitag_sensor.log import log\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\n\nclass ExitHandler(StreamHandler):\n\n    def emit(self, record):\n        if (record.levelname != \"DEBUG\"):\n            return\n        msg = self.format(record)\n        if \"Blacklisting MAC F4:A5:74:89:16:57E\" in msg:\n            exit(1)\n\n\nexit_handler = ExitHandler()\nlog.addHandler(exit_handler)\n\ndata = RuuviTagSensor.get_data_for_sensors()\n```\n\n## Command line application\n\n```\n$ python ruuvitag_sensor -h\n\nusage: ruuvitag_sensor [-h] [-g MAC_ADDRESS] [-d BT_DEVICE] [-f] [-l] [-s] [--version]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -g MAC_ADDRESS, --get MAC_ADDRESS\n                        Get data\n  -d BT_DEVICE, --device BT_DEVICE\n                        Set Bluetooth device id (default hci0)\n  -f, --find            Find broadcasting RuuviTags\n  -l, --latest          Get latest data for found RuuviTags\n  -s, --stream          Stream broadcasts from all RuuviTags\n  --version             show program's version number and exit\n```\n\n## BLE Communication modules\n\n### BlueZ\n\nBlueZ works only on __Linux__. When using BlueZ, Windows and macOS support is only for testing with hard-coded data and for data decoding.\n\nBlueZ tools require __superuser__ rights.\n\nInstall BlueZ.\n\n```sh\n$ sudo apt-get install bluez bluez-hcidump\n```\n\n`ruuvitag-sensor` package uses internally _hciconfig_, _hcitool_ and _hcidump_. These tools are deprecated. In case tools are missing, an older version of BlueZ is required ([Issue](https://github.com/ttu/ruuvitag-sensor/issues/31))\n\nIf you wish to test the library on Windows or macOS, enable it with `RUUVI_BLE_ADAPTER` environment variable.\n\n```sh\n$ export RUUVI_BLE_ADAPTER=\"bluez\"\n```\n\nAnd install ptyprocess.\n\n```sh\npython -m pip install ptyprocess\n```\n\n#### BlueZ limitations\n\n`ruuvitag-sensor` package uses BlueZ to listen to broadcasted BL information (uses _hciconf_, _hcitool_, _hcidump_). Implementation does not handle well all unexpected errors or changes, e.g. when the adapter is busy, rebooted or powered down.\n\nIn case of errors, the application tries to exit immediately, so it can be automatically restarted.\n\n### Bleak\n\nOn Windows and macOS Bleak is installed and used automatically with `ruuvitag-sensor` package. \n\nOn Linux install it manually from PyPI and enable it with `RUUVI_BLE_ADAPTER` environment variable.\n\n```sh\n$ python -m pip install bleak\n```\n\nAdd environment variable RUUVI_BLE_ADAPTER with value Bleak. E.g.\n\n```sh\n$ export RUUVI_BLE_ADAPTER=\"bleak\"\n```\n\nOr use `os.environ`. __NOTE:__ this must be set before importing `ruuvitag_sensor`.\n\n```py\nimport os\n\nos.environ[\"RUUVI_BLE_ADAPTER\"] = \"bleak\"\n```\n\nBleak supports only async methods.\n\n```py\nimport asyncio\nfrom ruuvitag_sensor.ruuvi import RuuviTagSensor\n\n\nasync def main():\n    async for data in RuuviTagSensor.get_data_async():\n        print(data)\n\n\nif __name__ == \"__main__\":\n    asyncio.get_event_loop().run_until_complete(main())\n```\n\nCheck [get_async_bleak](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/get_async_bleak.py) and other async examples from [examples](https://github.com/ttu/ruuvitag-sensor/tree/master/examples) directory.\n\n#### Bleak dummy BLE data\n\nBleak-adapter has a development-time generator for dummy data, which can be useful during the development, if no sensors are available. Set `RUUVI_BLE_ADAPTER` environment variable to `bleak_dev`.\n\n### Bleson\n\nCurrent state and known bugs in [issue #78](https://github.com/ttu/ruuvitag-sensor/issues/78).\n\n[Bleson](https://github.com/TheCellule/python-bleson) works with Linux, macOS and partially with Windows.\n\nBleson is not installed automatically with `ruuvitag-sensor` package. Install it manually from GitHub.\n\n```sh\n$ python -m pip install git+https://github.com/TheCellule/python-bleson\n```\n\nAdd environment variable `RUUVI_BLE_ADAPTER` with value `bleson`. E.g.\n\n```sh\n$ export RUUVI_BLE_ADAPTER=\"bleson\"\n```\n\n__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.\n\n__NOTE:__ On Windows, Bleson requires _Python 3.6_. Unfortunately on Windows, Bleson doesn't send any payload for the advertised package, so it is still unusable.\n\n\n## Python 2.x and 3.6 and below\n\nLast version of `ruuvitag-sensor` with Python 2.x and <3.7 support is [1.2.1](https://pypi.org/project/ruuvitag-sensor/1.2.1/).\n\n[Branch](https://github.com/ttu/ruuvitag-sensor/tree/release/1.2.1) / [Tag / commit](https://github.com/ttu/ruuvitag-sensor/commit/12ca3cfcb7fbed28477bb34f3bffd3eee0f9888d)\n\n```sh\n$ git checkout release/1.2.1\n```\n\nInstall from PyPI\n```sh\n$ python -m pip install ruuvitag-sensor==1.2.1\n```\n\n## Examples\n\nExamples are in [examples](https://github.com/ttu/ruuvitag-sensor/tree/master/examples) directory, e.g.\n\n* Keep track of each sensor's current status and send updated data to the server. [Sync](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/send_updated_sync.py) and [async](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/send_updated_async.py) version.\n* Send found sensor data to InfluxDB. [Reactive](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/post_to_influxdb_rx.py) and [non-reactive](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/post_to_influxdb.py) version. The naming convention of sent data matches [RuuviCollector library](https://github.com/scrin/ruuvicollector).\n* Simple HTTP Server for serving found sensor data. [Flask](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server.py), [aiohttp](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio.py) and [aiohttp with ReactiveX](https://github.com/ttu/ruuvitag-sensor/blob/master/examples/http_server_asyncio_rx.py).\n\n## Changelog\n\n[Changelog](https://github.com/ttu/ruuvitag-sensor/blob/master/CHANGELOG.md)\n\n## Developer notes\n\n[Notes for developers](https://github.com/ttu/ruuvitag-sensor/blob/master/developer_notes.md) who are interested in developing `ruuvitag-sensor` package or are interested in its internal functionality.\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\n## License\n\nLicensed under the [MIT](https://github.com/ttu/ruuvitag-sensor/blob/master/LICENSE) License.\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2016 Tomi Tuhkanen  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Find RuuviTag sensors and get decoded data from selected sensors",
    "version": "2.3.1",
    "project_urls": {
        "Bug Tracker": "https://github.com/ttu/ruuvitag-sensor/issues",
        "changelog": "https://github.com/ttu/ruuvitag-sensor/blob/master/CHANGELOG.md",
        "homepage": "https://github.com/ttu/ruuvitag-sensor",
        "source": "https://github.com/ttu/ruuvitag-sensor"
    },
    "split_keywords": [
        "ruuvitag",
        "ble"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f6606b7605bd0666911cdc3821f3f571fe028785fa50fbceca42e665a1a4213b",
                "md5": "db8cda508b21f742f26ec60c0a0d62cc",
                "sha256": "12967066c852bda9b0e60ce3150c24215631cc51ef5f62c27cc408a58510bf20"
            },
            "downloads": -1,
            "filename": "ruuvitag_sensor-2.3.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "db8cda508b21f742f26ec60c0a0d62cc",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 31180,
            "upload_time": "2024-03-10T15:30:30",
            "upload_time_iso_8601": "2024-03-10T15:30:30.926096Z",
            "url": "https://files.pythonhosted.org/packages/f6/60/6b7605bd0666911cdc3821f3f571fe028785fa50fbceca42e665a1a4213b/ruuvitag_sensor-2.3.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d8058fa451426802ed1a7403ea10ea3cff713140e291a6ddf77a6a70155dad2a",
                "md5": "75571667ea35c0be4f3b805f78ee5552",
                "sha256": "0babe33c919cb8b80fd82af2d13333affb8691d18fcf69f02e1bdbd0bac553fb"
            },
            "downloads": -1,
            "filename": "ruuvitag_sensor-2.3.1.tar.gz",
            "has_sig": false,
            "md5_digest": "75571667ea35c0be4f3b805f78ee5552",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 35489,
            "upload_time": "2024-03-10T15:30:33",
            "upload_time_iso_8601": "2024-03-10T15:30:33.335032Z",
            "url": "https://files.pythonhosted.org/packages/d8/05/8fa451426802ed1a7403ea10ea3cff713140e291a6ddf77a6a70155dad2a/ruuvitag_sensor-2.3.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-10 15:30:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ttu",
    "github_project": "ruuvitag-sensor",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "ruuvitag-sensor"
}
        
Elapsed time: 0.20107s