commit 5cb64e377ac01a0031e4ec6b05b5b4941a5bfa9f Author: Schemen Date: Wed Feb 17 17:58:11 2021 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dcf7d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,142 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Custom +config.ini diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8b0398 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# airthings-influxdb + +```x-sh +git clone ssh://git@git.ponz.io:10022/elia/airthings-influxdb.git && cd airthings-influxdb +virtualenv venv -p python3 +source venv/bin/activate +pip install -r requirements.txt +deactivate +``` diff --git a/airthings-influxdb.py b/airthings-influxdb.py new file mode 100755 index 0000000..c489af9 --- /dev/null +++ b/airthings-influxdb.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +import datetime +from os import environ as env +from time import sleep +import json + +import click +import configparser +from airthings import fetch_measurements, discover_devices +from influxdb import InfluxDBClient +from influxdb.exceptions import InfluxDBClientError + + +def read_config(file): + config = configparser.ConfigParser() + config.read(file) + return config + + +@click.group() +@click.option('--debug/--no-debug') +@click.option('-c', '--config', default="config.ini", help='give config file') +@click.pass_context +def cli(ctx, debug, config): + click.echo('Debug mode is %s' % ('on' if debug else 'off')) + ctx.ensure_object(dict) + ctx.obj['DEBUG'] = debug + ctx.obj['CONFIG'] = config + +@cli.command() +@click.pass_context +def verify(ctx): + """verify your configuration""" + config = read_config(ctx.obj['CONFIG']) + + influx_host = env.get("InfluxdbHost", config['DEFAULT']['InfluxdbHost']) + influx_port = env.get("InfluxdbPort", config['DEFAULT']['InfluxdbPort']) + influx_user = env.get("InfluxdbUser", config['DEFAULT']['InfluxdbUser']) + influx_pass = env.get("InfluxdbPassword", config['DEFAULT']['InfluxdbPassword']) + influx_db = env.get("InfluxdbDatabase", config['DEFAULT']['InfluxdbDatabase']) + + client = InfluxDBClient(influx_host, influx_port, influx_user, influx_pass, influx_db) + + +@cli.command() +def identify(): + """Lists available sensors nearby""" + + airthings_devices = fetch_measurements() + print("Found %s Airthings device(s):" % len(airthings_devices)) + for device in airthings_devices: + print("=" * 36) + print("\tMAC address:", device.mac_address) + print("\tIdentifier:", device.identifier) + print("\tModel:", device.label) + print("\tModel number:", device.model_number) + print("\tHas measurements:", device.has_measurements) + print("\tSensor capabilities:") + for sensor, supported in device.sensor_capabilities.items(): + print("\t", sensor, "=", "YES" if supported else "NO") + print("+" * 11, "Measurements", "+" * 11) + print("\t", device.humidity) + print("\t", device.radon_short_term_avg) + print("\t", device.radon_long_term_avg) + print("\t", device.temperature) + print("\t", device.atmospheric_pressure) + print("\t", device.co2) + print("\t", device.voc) + + + + +@cli.command() +@click.pass_context +def start(ctx): + """ + + """ + click.echo("Starting the Airthings to Influxdb Bridge") + click.echo("Reading the config...") + config = read_config(ctx.obj['CONFIG']) + + influx_host = env.get("InfluxdbHost", config['DEFAULT']['InfluxdbHost']) + influx_port = env.get("InfluxdbPort", config['DEFAULT']['InfluxdbPort']) + influx_user = env.get("InfluxdbUser", config['DEFAULT']['InfluxdbUser']) + influx_pass = env.get("InfluxdbPassword", config['DEFAULT']['InfluxdbPassword']) + influx_db = env.get("InfluxdbDatabase", config['DEFAULT']['InfluxdbDatabase']) + interval = env.get("Interval", config['DEFAULT']['Interval']) + + click.echo("Starting the Influxdb Client...") + + client = InfluxDBClient(influx_host, influx_port, influx_user, influx_pass, influx_db) + + data = [] + + click.echo("Starting sensor probing") + while True: + click.echo("Searching devices...") + airthings_devices = fetch_measurements() + print("Found %s Airthings devices:" % len(airthings_devices)) + for device in airthings_devices: + print("=" * 36) + print("\tMAC address:", device.mac_address) + print("\tIdentifier:", device.identifier) + print("\tModel:", device.label) + print("\tModel number:", device.model_number) + print("\tHas measurements:", device.has_measurements) + + click.echo("Collecting sensor data...") + now = datetime.datetime.now() + + if device.humidity: + json_body = { + "measurement": "humidity", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.humidity.value + } + } + data.append(json_body) + if device.radon_short_term_avg: + json_body = { + "measurement": "radon_short_term_avg", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.radon_short_term_avg.value + } + } + data.append(json_body) + if device.radon_long_term_avg: + json_body = { + "measurement": "radon_long_term_avg", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.radon_long_term_avg.value + } + } + data.append(json_body) + if device.temperature: + json_body = { + "measurement": "temperature", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.temperature.value + } + } + data.append(json_body) + if device.co2: + json_body = { + "measurement": "co2", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.co2.value + } + } + data.append(json_body) + if device.voc: + json_body = { + "measurement": "voc", + "tags": { + "mac address": device.mac_address, + "identifier": device.identifier, + "model": device.label, + "model number": device.model_number + }, + "time": now, + "fields": { + "value": device.voc.value + } + } + data.append(json_body) + + #write data + click.echo("Trying to send datapoints...") + + try: + client.write_points(data) + click.echo("Sent!") + except InfluxDBClientError as e: + click.echo("An error occured: %s" % e) + click.echo("Storing data for now, waiting for the next interval." % e) + click.echo("Sleeping for %s seconds" % interval) + sleep(int(interval)) + + + + +if __name__ == '__main__': + cli() diff --git a/config.ini.example b/config.ini.example new file mode 100644 index 0000000..0c0daed --- /dev/null +++ b/config.ini.example @@ -0,0 +1,7 @@ +[DEFAULT] +InfluxdbHost = influx.example.com +InfluxdbPort = 8086 +InfluxdbUser = airthings +InfluxdbPassword = airthings +InfluxdbDatabase = airthings +Interval = 60 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5bc3313 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +airthings==3.2.0 +influxdb==5.3.1 +click==7.1.2