diff --git a/enviroplus_exporter.py b/enviroplus_exporter.py index 0c15eb8..9b2075e 100755 --- a/enviroplus_exporter.py +++ b/enviroplus_exporter.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os import random +import requests import time import logging import argparse @@ -37,6 +38,8 @@ """) +DEBUG = os.getenv('DEBUG', 'false') == 'true' + bus = SMBus(1) bme280 = BME280(i2c_dev=bus) pms5003 = PMS5003() @@ -68,6 +71,9 @@ influxdb_client = InfluxDBClient(url=INFLUXDB_URL, token=INFLUXDB_TOKEN, org=INFLUXDB_ORG_ID) influxdb_api = influxdb_client.write_api(write_options=SYNCHRONOUS) +# Setup Luftdaten +LUFTDATEN_TIME_BETWEEN_POSTS = int(os.getenv('LUFTDATEN_TIME_BETWEEN_POSTS', '30')) + # Get the temperature of the CPU for compensation def get_cpu_temperature(): with open("/sys/class/thermal/thermal_zone0/temp", "r") as f: @@ -129,37 +135,104 @@ def get_particulates(): try: pms_data = pms5003.read() except pmsReadTimeoutError: - logging.warn("Failed to read PMS5003") + logging.warning("Failed to read PMS5003") else: PM1.set(pms_data.pm_ug_per_m3(1.0)) PM25.set(pms_data.pm_ug_per_m3(2.5)) PM10.set(pms_data.pm_ug_per_m3(10)) +def collect_all_data(): + """Collects all the data currently set""" + sensor_data = {} + sensor_data['temperature'] = TEMPERATURE.collect()[0].samples[0].value + sensor_data['humidity'] = HUMIDITY.collect()[0].samples[0].value + sensor_data['pressure'] = PRESSURE.collect()[0].samples[0].value + sensor_data['oxidising'] = OXIDISING.collect()[0].samples[0].value + sensor_data['reducing'] = REDUCING.collect()[0].samples[0].value + sensor_data['nh3'] = NH3.collect()[0].samples[0].value + sensor_data['lux'] = LUX.collect()[0].samples[0].value + sensor_data['proximity'] = PROXIMITY.collect()[0].samples[0].value + sensor_data['pm1'] = PM1.collect()[0].samples[0].value + sensor_data['pm25'] = PM25.collect()[0].samples[0].value + sensor_data['pm10'] = PM10.collect()[0].samples[0].value + return sensor_data def post_to_influxdb(): """Post all sensor data to InfluxDB""" + name = 'enviroplus' + tag = ['location', 'adelaide'] while True: time.sleep(INFLUXDB_TIME_BETWEEN_POSTS) - name = 'enviroplus' - tag = ['location', 'adelaide'] - fields = {} data_points = [] epoch_time_now = round(time.time()) - fields['temperature'] = TEMPERATURE.collect()[0].samples[0].value - fields['humidity'] = HUMIDITY.collect()[0].samples[0].value - fields['pressure'] = PRESSURE.collect()[0].samples[0].value - fields['oxidising'] = OXIDISING.collect()[0].samples[0].value - fields['reducing'] = REDUCING.collect()[0].samples[0].value - fields['nh3'] = NH3.collect()[0].samples[0].value - fields['lux'] = LUX.collect()[0].samples[0].value - fields['proximity'] = PROXIMITY.collect()[0].samples[0].value - fields['pm1'] = PM1.collect()[0].samples[0].value - fields['pm25'] = PM25.collect()[0].samples[0].value - fields['pm10'] = PM10.collect()[0].samples[0].value - for field_name in fields: - data_points.append(Point('enviroplus').tag('location', INFLUXDB_SENSOR_LOCATION).field(field_name, fields[field_name])) - influxdb_api.write(bucket=INFLUXDB_BUCKET, record=data_points) - + sensor_data = collect_all_data() + for field_name in sensor_data: + data_points.append(Point('enviroplus').tag('location', INFLUXDB_SENSOR_LOCATION).field(field_name, sensor_data[field_name])) + try: + influxdb_api.write(bucket=INFLUXDB_BUCKET, record=data_points) + if DEBUG: + logging.info('InfluxDB response: OK') + except Exception as exception: + logging.warning('Exception sending to InfluxDB: {}'.format(exception)) + +def post_to_luftdaten(): + """Post relevant sensor data to luftdaten.info""" + """Code from: https://github.com/sepulworld/balena-environ-plus""" + LUFTDATEN_SENSOR_UID = 'raspi-' + get_serial_number() + while True: + time.sleep(LUFTDATEN_TIME_BETWEEN_POSTS) + sensor_data = collect_all_data() + values = {} + values["P2"] = sensor_data['pm25'] + values["P1"] = sensor_data['pm10'] + values["temperature"] = "{:.2f}".format(sensor_data['temperature']) + values["pressure"] = "{:.2f}".format(sensor_data['pressure'] * 100) + values["humidity"] = "{:.2f}".format(sensor_data['humidity']) + pm_values = dict(i for i in values.items() if i[0].startswith('P')) + temperature_values = dict(i for i in values.items() if not i[0].startswith('P')) + try: + response_pin_1 = requests.post('https://api.luftdaten.info/v1/push-sensor-data/', + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": [{"value_type": key, "value": val} for + key, val in pm_values.items()] + }, + headers={ + "X-PIN": "1", + "X-Sensor": LUFTDATEN_SENSOR_UID, + "Content-Type": "application/json", + "cache-control": "no-cache" + } + ) + + response_pin_11 = requests.post('https://api.luftdaten.info/v1/push-sensor-data/', + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": [{"value_type": key, "value": val} for + key, val in temperature_values.items()] + }, + headers={ + "X-PIN": "11", + "X-Sensor": LUFTDATEN_SENSOR_UID, + "Content-Type": "application/json", + "cache-control": "no-cache" + } + ) + + if response_pin_1.ok and response_pin_11.ok: + if DEBUG: + logging.info('Luftdaten response: OK') + else: + logging.warning('Luftdaten response: Failed') + except Exception as exception: + logging.warning('Exception sending to Luftdaten: {}'.format(exception)) + +def get_serial_number(): + """Get Raspberry Pi serial number to use as LUFTDATEN_SENSOR_UID""" + with open('/proc/cpuinfo', 'r') as f: + for line in f: + if line[0:6] == 'Serial': + return str(line.split(":")[1].strip()) def str_to_bool(value): if value.lower() in {'false', 'f', '0', 'no', 'n'}: @@ -174,22 +247,34 @@ def str_to_bool(value): parser.add_argument("-b", "--bind", metavar='ADDRESS', default='0.0.0.0', help="Specify alternate bind address [default: 0.0.0.0]") parser.add_argument("-p", "--port", metavar='PORT', default=8000, type=int, help="Specify alternate port [default: 8000]") parser.add_argument("-f", "--factor", metavar='FACTOR', type=float, help="The compensation factor to get better temperature results when the Enviro+ pHAT is too close to the Raspberry Pi board") + parser.add_argument("-d", "--debug", metavar='DEBUG', type=str_to_bool, help="Turns on more vebose logging, showing sensor output and post responses [default: false]") parser.add_argument("-i", "--influxdb", metavar='INFLUXDB', type=str_to_bool, default='false', help="Post sensor data to InfluxDB [default: false]") + parser.add_argument("-l", "--luftdaten", metavar='LUFTDATEN', type=str_to_bool, default='false', help="Post sensor data to Luftdaten [default: false]") args = parser.parse_args() # Start up the server to expose the metrics. start_http_server(addr=args.bind, port=args.port) # Generate some requests. - + + if args.debug: + DEBUG = True + if args.factor: logging.info("Using compensating algorithm (factor={}) to account for heat leakage from Raspberry Pi board".format(args.factor)) if args.influxdb: - # Post to InfluxDB on another thread + # Post to InfluxDB in another thread logging.info("Sensor data will be posted to InfluxDB every {} seconds".format(INFLUXDB_TIME_BETWEEN_POSTS)) influx_thread = Thread(target=post_to_influxdb) influx_thread.start() + if args.luftdaten: + # Post to Luftdaten in another thread + LUFTDATEN_SENSOR_UID = 'raspi-' + get_serial_number() + logging.info("Sensor data will be posted to Luftdaten every {} seconds for the UID {}".format(LUFTDATEN_TIME_BETWEEN_POSTS, LUFTDATEN_SENSOR_UID)) + luftdaten_thread = Thread(target=post_to_luftdaten) + luftdaten_thread.start() + logging.info("Listening on http://{}:{}".format(args.bind, args.port)) while True: @@ -199,3 +284,5 @@ def str_to_bool(value): get_gas() get_light() get_particulates() + if DEBUG: + logging.info('Sensor data: {}'.format(collect_all_data()))