From 04565414bfd12051093ad7fa1a5815e06953edda Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 19 Aug 2019 11:02:52 +0100 Subject: [PATCH 01/69] Improvements for #28 --- examples/all-in-one-no-pm.py | 8 ++++---- examples/all-in-one.py | 8 ++++---- examples/luftdaten.py | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/all-in-one-no-pm.py b/examples/all-in-one-no-pm.py index d9b1069c..959303e3 100755 --- a/examples/all-in-one-no-pm.py +++ b/examples/all-in-one-no-pm.py @@ -14,7 +14,6 @@ from bme280 import BME280 from enviroplus import gas -from subprocess import PIPE, Popen from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -91,9 +90,10 @@ def display_text(variable, data, unit): # Get the temperature of the CPU for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) - output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + with open("/sys/class/thermal/thermal_zone0/temp", "r") as f: + temp = f.read() + temp = int(temp) / 1000.0 + return temp # Tuning factor for compensation. Decrease this number to adjust the diff --git a/examples/all-in-one.py b/examples/all-in-one.py index c0423e6d..8b84a444 100755 --- a/examples/all-in-one.py +++ b/examples/all-in-one.py @@ -15,7 +15,6 @@ from bme280 import BME280 from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError from enviroplus import gas -from subprocess import PIPE, Popen from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -96,9 +95,10 @@ def display_text(variable, data, unit): # Get the temperature of the CPU for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) - output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + with open("/sys/class/thermal/thermal_zone0/temp", "r") as f: + temp = f.read() + temp = int(temp) / 1000.0 + return temp # Tuning factor for compensation. Decrease this number to adjust the diff --git a/examples/luftdaten.py b/examples/luftdaten.py index d2d65621..3270f7d1 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -71,11 +71,12 @@ def read_values(): return values -# Get CPU temperature to use for compensation +# Get the temperature of the CPU for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) - output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + with open("/sys/class/thermal/thermal_zone0/temp", "r") as f: + temp = f.read() + temp = int(temp) / 1000.0 + return temp # Get Raspberry Pi serial number to use as ID From 124f49bc4cfc130ff31890767b8c994b0686cde0 Mon Sep 17 00:00:00 2001 From: Ross Fowler Date: Mon, 10 Aug 2020 08:51:43 +1000 Subject: [PATCH 02/69] Update luftdaten.py Add Logging, PMS5003 Checksum exception and Luftdaten exception handling as per Issue #81 --- examples/luftdaten.py | 133 ++++++++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 50 deletions(-) diff --git a/examples/luftdaten.py b/examples/luftdaten.py index 84f11177..7701a4ae 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -4,7 +4,7 @@ import ST7735 import time from bme280 import BME280 -from pms5003 import PMS5003, ReadTimeoutError +from pms5003 import PMS5003, ReadTimeoutError, ChecksumMismatchError from subprocess import PIPE, Popen, check_output from PIL import Image, ImageDraw, ImageFont from fonts.ttf import RobotoMedium as UserFont @@ -13,20 +13,26 @@ from smbus2 import SMBus except ImportError: from smbus import SMBus +import logging -print("""luftdaten.py - Reads temperature, pressure, humidity, -PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, -the citizen science air quality project. +logging.basicConfig( + format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') -Note: you'll need to register with Luftdaten at: -https://meine.luftdaten.info/ and enter your Raspberry Pi -serial number that's displayed on the Enviro plus LCD along -with the other details before the data appears on the -Luftdaten map. +logging.info("""luftdaten.py - Reads temperature, pressure, humidity, +#PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, +#the citizen science air quality project. -Press Ctrl+C to exit! +#Note: you'll need to register with Luftdaten at: +#https://meine.luftdaten.info/ and enter your Raspberry Pi +#serial number that's displayed on the Enviro plus LCD along +#with the other details before the data appears on the +#Luftdaten map. -""") +#Press Ctrl+C to exit! + +#""") bus = SMBus(1) @@ -63,7 +69,8 @@ def read_values(): pm_values = pms5003.read() values["P2"] = str(pm_values.pm_ug_per_m3(2.5)) values["P1"] = str(pm_values.pm_ug_per_m3(10)) - except ReadTimeoutError: + except(ReadTimeoutError, ChecksumMismatchError): + logging.info("Failed to read PMS5003. Reseting and retrying.") pms5003.reset() pm_values = pms5003.read() values["P2"] = str(pm_values.pm_ug_per_m3(2.5)) @@ -117,37 +124,63 @@ def send_to_luftdaten(values, id): pm_values_json = [{"value_type": key, "value": val} for key, val in pm_values.items()] temp_values_json = [{"value_type": key, "value": val} for key, val in temp_values.items()] - - resp_1 = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", - json={ - "software_version": "enviro-plus 0.0.1", - "sensordatavalues": pm_values_json - }, - headers={ - "X-PIN": "1", - "X-Sensor": id, - "Content-Type": "application/json", - "cache-control": "no-cache" - } - ) - - resp_2 = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", - json={ - "software_version": "enviro-plus 0.0.1", - "sensordatavalues": temp_values_json - }, - headers={ - "X-PIN": "11", - "X-Sensor": id, - "Content-Type": "application/json", - "cache-control": "no-cache" - } - ) - - if resp_1.ok and resp_2.ok: - return True + resp1_exception = False + resp2_exception = False + try: + resp_1 = requests.post( + "https://api.luftdaten.info/v1/push-sensor-data/", + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": pm_values_json + }, + headers={ + "X-PIN": "1", + "X-Sensor": id, + "Content-Type": "application/json", + "cache-control": "no-cache" + }, + timeout=5 + ) + except requests.exceptions.ConnectionError as e: + resp1_exception = True + logging.info('Luftdaten PM Connection Error: ' + str(e)) + except requests.exceptions.Timeout as e: + resp1_exception = True + logging.info('Luftdaten PM Timeout Error: ' + str(e)) + except requests.exceptions.RequestException as e: + resp1_exception = True + logging.info('Luftdaten PM Request Error: ' + str(e)) + + try: + resp_2 = requests.post( + "https://api.luftdaten.info/v1/push-sensor-data/", + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": temp_values_json + }, + headers={ + "X-PIN": "11", + "X-Sensor": id, + "Content-Type": "application/json", + "cache-control": "no-cache" + }, + timeout=5 + ) + except requests.exceptions.ConnectionError as e: + resp2_exception = True + logging.info('Luftdaten Climate Connection Error: ' + str(e)) + except requests.exceptions.Timeout as e: + resp2_exception = True + logging.info('Luftdaten Climate Timeout Error: ' + str(e)) + except requests.exceptions.RequestException as e: + resp2_exception = True + logging.info('Luftdaten Climate Request Error: ' + str(e)) + + if not resp1_exception and not resp2_exception: + if resp_1.ok and resp_2.ok: + return True + else: + return False else: return False @@ -166,9 +199,9 @@ def send_to_luftdaten(values, id): font_size = 16 font = ImageFont.truetype(UserFont, font_size) -# Display Raspberry Pi serial and Wi-Fi status -print("Raspberry Pi serial: {}".format(get_serial_number())) -print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) +# Log Raspberry Pi serial and Wi-Fi status +logging.info("Raspberry Pi serial: {}".format(get_serial_number())) +logging.info("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) time_since_update = 0 update_time = time.time() @@ -176,13 +209,13 @@ def send_to_luftdaten(values, id): # Main loop to read data, display, and send to Luftdaten while True: try: - time_since_update = time.time() - update_time values = read_values() - print(values) + logging.info(values) + time_since_update = time.time() - update_time if time_since_update > 145: resp = send_to_luftdaten(values, id) update_time = time.time() - print("Response: {}\n".format("ok" if resp else "failed")) + logging.info("Luftdaten Response: {}\n".format("ok" if resp else "failed")) display_status() except Exception as e: - print(e) + logging.info("Main Loop Exception: " + str(e)) From 1a10d4bfb81d15b9355fbb668902bcadfc33dc5a Mon Sep 17 00:00:00 2001 From: Ross Fowler Date: Fri, 14 Aug 2020 20:07:31 +1000 Subject: [PATCH 03/69] Update luftdaten.py Added suggested changes but removed "logging.info('Luftdaten Climate Success', values)", since I found it redundant, given the use of "logging.info("Luftdaten Response: OK")" in Line 215. --- examples/luftdaten.py | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/examples/luftdaten.py b/examples/luftdaten.py index 7701a4ae..0c13771b 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -124,10 +124,12 @@ def send_to_luftdaten(values, id): pm_values_json = [{"value_type": key, "value": val} for key, val in pm_values.items()] temp_values_json = [{"value_type": key, "value": val} for key, val in temp_values.items()] - resp1_exception = False - resp2_exception = False + + resp_pm = None + resp_bmp = None + try: - resp_1 = requests.post( + resp_pm = requests.post( "https://api.luftdaten.info/v1/push-sensor-data/", json={ "software_version": "enviro-plus 0.0.1", @@ -142,17 +144,14 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - resp1_exception = True - logging.info('Luftdaten PM Connection Error: ' + str(e)) + logging.warning('Luftdaten PM Connection Error: {}'.format(e)) except requests.exceptions.Timeout as e: - resp1_exception = True - logging.info('Luftdaten PM Timeout Error: ' + str(e)) + logging.warning('Luftdaten PM Timeout Error: {}'.format(e)) except requests.exceptions.RequestException as e: - resp1_exception = True - logging.info('Luftdaten PM Request Error: ' + str(e)) + logging.warning('Luftdaten PM Request Error: {}'.format(e)) try: - resp_2 = requests.post( + resp_bmp = requests.post( "https://api.luftdaten.info/v1/push-sensor-data/", json={ "software_version": "enviro-plus 0.0.1", @@ -167,19 +166,17 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - resp2_exception = True - logging.info('Luftdaten Climate Connection Error: ' + str(e)) + logging.warning('Luftdaten Climate Connection Error: {}'.format(e)) except requests.exceptions.Timeout as e: - resp2_exception = True - logging.info('Luftdaten Climate Timeout Error: ' + str(e)) + logging.warning('Luftdaten Climate Timeout Error: {}'.format(e)) except requests.exceptions.RequestException as e: - resp2_exception = True - logging.info('Luftdaten Climate Request Error: ' + str(e)) + logging.warning('Luftdaten Climate Request Error: {}'.format(e)) - if not resp1_exception and not resp2_exception: - if resp_1.ok and resp_2.ok: + if resp_pm is not None and resp_bmp is not None: + if resp_pm.ok and resp_bmp.ok: return True else: + logging.warning('Luftdaten Error. PM: {}, Climate: {}'.format(resp_pm.reason, resp_bmp.reason)) return False else: return False @@ -210,12 +207,14 @@ def send_to_luftdaten(values, id): while True: try: values = read_values() - logging.info(values) time_since_update = time.time() - update_time if time_since_update > 145: - resp = send_to_luftdaten(values, id) + logging.info(values) update_time = time.time() - logging.info("Luftdaten Response: {}\n".format("ok" if resp else "failed")) + if send_to_luftdaten(values, id): + logging.info("Luftdaten Response: OK") + else: + logging.warning("Luftdaten Response: Failed") display_status() except Exception as e: - logging.info("Main Loop Exception: " + str(e)) + logging.warning('Main Loop Exception: {}'.format(e)) From f7229e09ecc0da158d9b7c02f5ab13ad219c5edb Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 17 Dec 2020 11:46:47 -0500 Subject: [PATCH 04/69] Catching errors With no PMS sensor attached you cannot flip through the screens ( tapping the light sensor until you reach the PMS page will throw uncaught exception. Added exception to the three cases, in the same manner as the main page --- examples/combined.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/combined.py b/examples/combined.py index 6c4ab6f5..556bcb0c 100755 --- a/examples/combined.py +++ b/examples/combined.py @@ -275,7 +275,7 @@ def main(): unit = "ug/m3" try: data = pms5003.read() - except pmsReadTimeoutError: + except (SerialTimeoutError, pmsReadTimeoutError): logging.warning("Failed to read PMS5003") else: data = float(data.pm_ug_per_m3(1.0)) @@ -286,7 +286,7 @@ def main(): unit = "ug/m3" try: data = pms5003.read() - except pmsReadTimeoutError: + except (SerialTimeoutError, pmsReadTimeoutError): logging.warning("Failed to read PMS5003") else: data = float(data.pm_ug_per_m3(2.5)) @@ -297,7 +297,7 @@ def main(): unit = "ug/m3" try: data = pms5003.read() - except pmsReadTimeoutError: + except (SerialTimeoutError, pmsReadTimeoutError): logging.warning("Failed to read PMS5003") else: data = float(data.pm_ug_per_m3(10)) From aeaad174b8da7aa5dabc31a126fa2e287c98f18e Mon Sep 17 00:00:00 2001 From: Peter Armstrong Date: Sun, 20 Dec 2020 12:46:19 +0000 Subject: [PATCH 05/69] adds username and password parameters to mqtt-all --- examples/mqtt-all.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 0eff47a1..2e9df340 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -1,7 +1,7 @@ """ Run mqtt broker on localhost: sudo apt-get install mosquitto mosquitto-clients -Example run: python3 mqtt-all.py --broker 192.168.1.164 --topic enviro +Example run: python3 mqtt-all.py --broker 192.168.1.164 --topic enviro --username xxx --password xxxx """ #!/usr/bin/env python3 @@ -165,6 +165,19 @@ def main(): type=int, help="the read interval in seconds", ) + parser.add_argument( + "--username", + default=None, + type=str, + help="mqtt username", + ) + parser.add_argument( + "--password", + default=None, + type=str, + help="mqtt password", + ) + args = parser.parse_args() # Raspberry Pi ID @@ -178,6 +191,8 @@ def main(): client_id: {device_id} port: {args.port} topic: {args.topic} + username: {args.username} + password: {args.password} Press Ctrl+C to exit! @@ -185,6 +200,7 @@ def main(): ) mqtt_client = mqtt.Client(client_id=device_id) + mqtt_client.username_pw_set(args.username, args.password) mqtt_client.on_connect = on_connect mqtt_client.on_publish = on_publish mqtt_client.connect(args.broker, port=args.port) From d2f4688195b6aa5b1beb4745623672ed854ef63c Mon Sep 17 00:00:00 2001 From: Peter Armstrong Date: Mon, 4 Jan 2021 12:16:13 +0000 Subject: [PATCH 06/69] adds username and password check --- examples/mqtt-all.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 2e9df340..07eecccc 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -200,7 +200,8 @@ def main(): ) mqtt_client = mqtt.Client(client_id=device_id) - mqtt_client.username_pw_set(args.username, args.password) + if username and password: + mqtt_client.username_pw_set(args.username, args.password) mqtt_client.on_connect = on_connect mqtt_client.on_publish = on_publish mqtt_client.connect(args.broker, port=args.port) From ae3e87dc933d1e94548f40e1903027fc7b2e7096 Mon Sep 17 00:00:00 2001 From: James Sutton <1068763+jpwsutton@users.noreply.github.com> Date: Fri, 19 Feb 2021 20:07:48 +0000 Subject: [PATCH 07/69] Adding MQTT Username / Password & TLS Config Signed-off-by: James Sutton <1068763+jpwsutton@users.noreply.github.com> --- examples/mqtt-all.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 0eff47a1..d531120e 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -8,6 +8,7 @@ import argparse import ST7735 import time +import ssl from bme280 import BME280 from pms5003 import PMS5003, ReadTimeoutError, SerialTimeoutError from enviroplus import gas @@ -38,6 +39,9 @@ DEFAULT_MQTT_BROKER_PORT = 1883 DEFAULT_MQTT_TOPIC = "enviroplus" DEFAULT_READ_INTERVAL = 5 +DEFAULT_TLS_MODE = False +DEFAULT_USERNAME = None +DEFAULT_PASSWORD = None # mqtt callbacks def on_connect(client, userdata, flags, rc): @@ -165,6 +169,24 @@ def main(): type=int, help="the read interval in seconds", ) + parser.add_argument( + "--tls", + default=DEFAULT_TLS_MODE, + type=bool, + help="enable TLS" + ) + parser.add_argument( + "--username", + default=DEFAULT_USERNAME, + type=str, + help="mqtt username" + ) + parser.add_argument( + "--password", + default=DEFAULT_PASSWORD, + type=str, + help="mqtt password" + ) args = parser.parse_args() # Raspberry Pi ID @@ -178,6 +200,9 @@ def main(): client_id: {device_id} port: {args.port} topic: {args.topic} + tls: {args.tls} + username: {args.username} + password: {args.password} Press Ctrl+C to exit! @@ -187,6 +212,13 @@ def main(): mqtt_client = mqtt.Client(client_id=device_id) mqtt_client.on_connect = on_connect mqtt_client.on_publish = on_publish + + if args.tls is True: + mqtt_client.tls_set(tls_version=ssl.PROTOCOL_TLSv1_2) + + if args.username is not None: + mqtt_client.username_pw_set(args.username, password=args.password) + mqtt_client.connect(args.broker, port=args.port) bus = SMBus(1) From 2dbabe56cd8cddb2a8c641a9b545eacc80e0c4f0 Mon Sep 17 00:00:00 2001 From: James Sutton <1068763+jpwsutton@users.noreply.github.com> Date: Fri, 19 Feb 2021 20:23:24 +0000 Subject: [PATCH 08/69] Tweaking the arguments for MQTT TLS Signed-off-by: James Sutton <1068763+jpwsutton@users.noreply.github.com> --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index d531120e..9735ddcd 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -172,7 +172,7 @@ def main(): parser.add_argument( "--tls", default=DEFAULT_TLS_MODE, - type=bool, + action='store_true', help="enable TLS" ) parser.add_argument( From 2ef6bafef176ccffbd4371a464854372cc8687a6 Mon Sep 17 00:00:00 2001 From: Lucas Dodgson Date: Mon, 8 Mar 2021 16:22:49 +0100 Subject: [PATCH 09/69] Added a file with the functionality of combined, while also sharing the data with luftdaten --- examples/luftdaten_combined.py | 445 +++++++++++++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 examples/luftdaten_combined.py diff --git a/examples/luftdaten_combined.py b/examples/luftdaten_combined.py new file mode 100644 index 00000000..1d920db4 --- /dev/null +++ b/examples/luftdaten_combined.py @@ -0,0 +1,445 @@ +import logging +import sys +import requests +import ST7735 +import time +import colorsys +from bme280 import BME280 +from pms5003 import PMS5003, ReadTimeoutError +from subprocess import PIPE, Popen, check_output +from PIL import Image, ImageDraw, ImageFont +from fonts.ttf import RobotoMedium as UserFont +from enviroplus import gas + +try: + from smbus2 import SMBus +except ImportError: + from smbus import SMBus +try: + # Transitional fix for breaking change in LTR559 + from ltr559 import LTR559 + ltr559 = LTR559() +except ImportError: + import ltr559 + +print("""luftdaten_combined.py - This combines the functionality of luftdaten.py and combined.py +================================================================================================ +Luftdaten INFO +Reads temperature, pressure, humidity, +PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, +the citizen science air quality project. + +Note: you'll need to register with Luftdaten at: +https://meine.luftdaten.info/ and enter your Raspberry Pi +serial number that's displayed on the Enviro plus LCD along +with the other details before the data appears on the +Luftdaten map. + +Press Ctrl+C to exit! + +======================================================================== + +Combined INFO: +Displays readings from all of Enviro plus' sensors + +Press Ctrl+C to exit! + +""") + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + +logging.info(""" """) +bus = SMBus(1) + +# Create BME280 instance +bme280 = BME280(i2c_dev=bus) + + +# Create PMS5003 instance +pms5003 = PMS5003() + +# Create a values dict to store the data +variables = ["temperature", + "pressure", + "humidity", + "light", + "oxidised", + "reduced", + "nh3", + "pm1", + "pm25", + "pm10"] +units = ["C", + "hPa", + "%", + "Lux", + "kO", + "kO", + "kO", + "ug/m3", + "ug/m3", + "ug/m3"] + +# Define your own warning limits +# The limits definition follows the order of the variables array +# Example limits explanation for temperature: +# [4,18,28,35] means +# [-273.15 .. 4] -> Dangerously Low +# (4 .. 18] -> Low +# (18 .. 28] -> Normal +# (28 .. 35] -> High +# (35 .. MAX] -> Dangerously High +# DISCLAIMER: The limits provided here are just examples and come +# with NO WARRANTY. The authors of this example code claim +# NO RESPONSIBILITY if reliance on the following values or this +# code in general leads to ANY DAMAGES or DEATH. +limits = [[4, 18, 25, 35], + [250, 650, 1013.25, 1015], + [20, 30, 60, 70], + [-1, -1, 30000, 100000], + [-1, -1, 40, 50], + [-1, -1, 450, 550], + [-1, -1, 200, 300], + [-1, -1, 50, 100], + [-1, -1, 50, 100], + [-1, -1, 50, 100]] + +# RGB palette for values on the combined screen +palette = [(0, 0, 255), # Dangerously Low + (0, 255, 255), # Low + (0, 255, 0), # Normal + (255, 255, 0), # High + (255, 0, 0)] # Dangerously High +values_lcd = {} + + +# Read values from BME280 and PMS5003 and return as dict +def read_values(comp_temp, mod_press, raw_humid, raw_pm25, raw_pm10): + values = {} + values["temperature"] = "{:.2f}".format(comp_temp) + values["pressure"] = "{:.2f}".format(mod_press) + values["humidity"] = "{:.2f}".format(raw_humid) + values["P2"] = str(raw_pm25) + values["P1"] = str(raw_pm10) + return values + + +# Get CPU temperature to use for compensation +def get_cpu_temperature(): + process = Popen(['vcgencmd', 'measure_temp'], + stdout=PIPE, universal_newlines=True) + output, _error = process.communicate() + return float(output[output.index('=') + 1:output.rindex("'")]) + + +# Get Raspberry Pi serial number to use as ID +def get_serial_number(): + with open('/proc/cpuinfo', 'r') as f: + for line in f: + if line[0:6] == 'Serial': + return line.split(":")[1].strip() + + +# Check for Wi-Fi connection +def check_wifi(): + if check_output(['hostname', '-I']): + return True + else: + return False + + +# Create ST7735 LCD display class +st7735 = ST7735.ST7735( + port=0, + cs=1, + dc=9, + backlight=12, + rotation=270, + spi_speed_hz=10000000 +) + +# Initialize display +st7735.begin() + +WIDTH = st7735.width +HEIGHT = st7735.height + +# Set up canvas and font +img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +draw = ImageDraw.Draw(img) +font_size_small = 10 +font_size_large = 20 +font = ImageFont.truetype(UserFont, font_size_large) +smallfont = ImageFont.truetype(UserFont, font_size_small) +x_offset = 2 +y_offset = 2 +message = "" + +# The position of the top bar +top_pos = 25 + +# Saves the data to be used in the graphs later and prints to the log + + +def save_data(idx, data): + variable = variables[idx] + # Maintain length of list + values_lcd[variable] = values_lcd[variable][1:] + [data] + unit = units[idx] + message = "{}: {:.1f} {}".format(variable[:4], data, unit) + logging.info(message) + + +# Displays data and text on the 0.96" LCD +def display_text(variable, data, unit): + # Maintain length of list + values_lcd[variable] = values_lcd[variable][1:] + [data] + # Scale the values for the variable between 0 and 1 + vmin = min(values_lcd[variable]) + vmax = max(values_lcd[variable]) + colours = [(v - vmin + 1) / (vmax - vmin + 1) + for v in values_lcd[variable]] + # Format the variable name and value + message = "{}: {:.1f} {}".format(variable[:4], data, unit) + logging.info(message) + draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) + for i in range(len(colours)): + # Convert the values to colours from red to blue + colour = (1.0 - colours[i]) * 0.6 + r, g, b = [int(x * 255.0) + for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)] + # Draw a 1-pixel wide rectangle of colour + draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b)) + # Draw a line graph in black + line_y = HEIGHT - \ + (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos + draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0)) + # Write the text at the top in black + draw.text((0, 0), message, font=font, fill=(0, 0, 0)) + st7735.display(img) + +# Displays all the text on the 0.96" LCD + + +def display_everything(): + draw.rectangle((0, 0, WIDTH, HEIGHT), (0, 0, 0)) + column_count = 2 + row_count = (len(variables) / column_count) + for i in range(len(variables)): + variable = variables[i] + data_value = values_lcd[variable][-1] + unit = units[i] + x = x_offset + ((WIDTH // column_count) * (i // row_count)) + y = y_offset + ((HEIGHT / row_count) * (i % row_count)) + message = "{}: {:.1f} {}".format(variable[:4], data_value, unit) + lim = limits[i] + rgb = palette[0] + for j in range(len(lim)): + if data_value > lim[j]: + rgb = palette[j + 1] + draw.text((x, y), message, font=smallfont, fill=rgb) + st7735.display(img) + + +def send_to_luftdaten(values, id): + pm_values = dict(i for i in values.items() if i[0].startswith("P")) + temp_values = dict(i for i in values.items() if not i[0].startswith("P")) + + pm_values_json = [{"value_type": key, "value": val} + for key, val in pm_values.items()] + temp_values_json = [{"value_type": key, "value": val} + for key, val in temp_values.items()] + + resp_1 = requests.post( + "https://api.luftdaten.info/v1/push-sensor-data/", + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": pm_values_json + }, + headers={ + "X-PIN": "1", + "X-Sensor": id, + "Content-Type": "application/json", + "cache-control": "no-cache" + } + ) + + resp_2 = requests.post( + "https://api.luftdaten.info/v1/push-sensor-data/", + json={ + "software_version": "enviro-plus 0.0.1", + "sensordatavalues": temp_values_json + }, + headers={ + "X-PIN": "11", + "X-Sensor": id, + "Content-Type": "application/json", + "cache-control": "no-cache" + } + ) + + if resp_1.ok and resp_2.ok: + return True + else: + return False + + +# Compensation factor for temperature +comp_factor = 1 + +# Raspberry Pi ID to send to Luftdaten +id = "raspi-" + get_serial_number() + + +# Added for state +delay = 0.5 # Debounce the proximity tap +mode = 10 # The starting mode +last_page = 0 +light = 1 + + +for v in variables: + values_lcd[v] = [1] * WIDTH + + +# Text settings +font_size = 16 +font = ImageFont.truetype(UserFont, font_size) +cpu_temps = [get_cpu_temperature()] * 5 + +# Display Raspberry Pi serial and Wi-Fi status +print("Raspberry Pi serial: {}".format(get_serial_number())) +print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) + +time_since_update = 0 +update_time = time.time() +cpu_temps_len = float(len(cpu_temps)) + +# Main loop to read data, display, and send to Luftdaten +while True: + try: + curtime = time.time() + time_since_update = curtime - update_time + + # Calculate these things once, not twice + cpu_temp = get_cpu_temperature() + # Smooth out with some averaging to decrease jitter + cpu_temps = cpu_temps[1:] + [cpu_temp] + avg_cpu_temp = sum(cpu_temps) / cpu_temps_len + raw_temp = bme280.get_temperature() + comp_temp = raw_temp - ((avg_cpu_temp - raw_temp) / comp_factor) + + raw_press = bme280.get_pressure() + raw_humid = bme280.get_humidity() + try: + pm_values = pms5003.read() + raw_pm25 = pm_values.pm_ug_per_m3(2.5) + raw_pm10 = pm_values.pm_ug_per_m3(10) + except ReadTimeoutError: + pms5003.reset() + pm_values = pms5003.read() + raw_pm25 = pm_values.pm_ug_per_m3(2.5) + raw_pm10 = pm_values.pm_ug_per_m3(10) + + if time_since_update > 145: + values = read_values(comp_temp, raw_press*100, + raw_humid, raw_pm25, raw_pm10) + resp = send_to_luftdaten(values, id) + update_time = curtime + print("Response: {}\n".format("ok" if resp else "failed")) + + # Now comes the combined.py functionality: + # If the proximity crosses the threshold, toggle the mode + proximity = ltr559.get_proximity() + if proximity > 1500 and curtime - last_page > delay: + mode = (mode + 1) % 11 + last_page = curtime + # One mode for each variable + if mode == 0: + # variable = "temperature" + unit = "C" + display_text(variables[mode], comp_temp, unit) + + if mode == 1: + # variable = "pressure" + unit = "hPa" + display_text(variables[mode], raw_press, unit) + + if mode == 2: + # variable = "humidity" + unit = "%" + display_text(variables[mode], raw_humid, unit) + + if mode == 3: + # variable = "light" + unit = "Lux" + if proximity < 10: + data = ltr559.get_lux() + else: + data = 1 + display_text(variables[mode], data, unit) + + if mode == 4: + # variable = "oxidised" + unit = "kO" + data = gas.read_all() + data = data.oxidising / 1000 + display_text(variables[mode], data, unit) + + if mode == 5: + # variable = "reduced" + unit = "kO" + data = gas.read_all() + data = data.reducing / 1000 + display_text(variables[mode], data, unit) + + if mode == 6: + # variable = "nh3" + unit = "kO" + data = gas.read_all() + data = data.nh3 / 1000 + display_text(variables[mode], data, unit) + + if mode == 7: + # variable = "pm1" + unit = "ug/m3" + data = float(pm_values.pm_ug_per_m3(1.0)) + display_text(variables[mode], data, unit) + + if mode == 8: + # variable = "pm25" + unit = "ug/m3" + display_text(variables[mode], float(raw_pm25), unit) + + if mode == 9: + # variable = "pm10" + unit = "ug/m3" + display_text(variables[mode], float(raw_pm10), unit) + + if mode == 10: + # Everything on one screen + save_data(0, comp_temp) + save_data(1, raw_press) + display_everything() + save_data(2, raw_humid) + if proximity < 10: + raw_data = ltr559.get_lux() + else: + raw_data = 1 + save_data(3, raw_data) + display_everything() + gas_data = gas.read_all() + save_data(4, gas_data.oxidising / 1000) + save_data(5, gas_data.reducing / 1000) + save_data(6, gas_data.nh3 / 1000) + display_everything() + pms_data = None + save_data(7, float(pm_values.pm_ug_per_m3(1.0))) + save_data(8, float(raw_pm25)) + save_data(9, float(raw_pm10)) + display_everything() + except Exception as e: + print(e) From 8e62b4f76481ff38831beef1b86d2f1cca77a4f2 Mon Sep 17 00:00:00 2001 From: Peter Armstrong Date: Tue, 13 Apr 2021 13:02:36 +0100 Subject: [PATCH 10/69] fix checking for username and password --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 07eecccc..5f25e393 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -200,7 +200,7 @@ def main(): ) mqtt_client = mqtt.Client(client_id=device_id) - if username and password: + if args.username and args.password: mqtt_client.username_pw_set(args.username, args.password) mqtt_client.on_connect = on_connect mqtt_client.on_publish = on_publish From ba1042d0b2245f9ad5d0d12ac55c01f7b66fa223 Mon Sep 17 00:00:00 2001 From: James Sutton <1068763+jpwsutton@users.noreply.github.com> Date: Fri, 19 Feb 2021 20:07:48 +0000 Subject: [PATCH 11/69] Adding MQTT Username / Password & TLS Config Signed-off-by: James Sutton <1068763+jpwsutton@users.noreply.github.com> --- examples/mqtt-all.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 5f25e393..895a8a26 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -8,6 +8,7 @@ import argparse import ST7735 import time +import ssl from bme280 import BME280 from pms5003 import PMS5003, ReadTimeoutError, SerialTimeoutError from enviroplus import gas @@ -38,6 +39,9 @@ DEFAULT_MQTT_BROKER_PORT = 1883 DEFAULT_MQTT_TOPIC = "enviroplus" DEFAULT_READ_INTERVAL = 5 +DEFAULT_TLS_MODE = False +DEFAULT_USERNAME = None +DEFAULT_PASSWORD = None # mqtt callbacks def on_connect(client, userdata, flags, rc): @@ -165,19 +169,24 @@ def main(): type=int, help="the read interval in seconds", ) + parser.add_argument( + "--tls", + default=DEFAULT_TLS_MODE, + type=bool, + help="enable TLS" + ) parser.add_argument( "--username", - default=None, + default=DEFAULT_USERNAME, type=str, - help="mqtt username", + help="mqtt username" ) parser.add_argument( "--password", - default=None, + default=DEFAULT_PASSWORD, type=str, - help="mqtt password", + help="mqtt password" ) - args = parser.parse_args() # Raspberry Pi ID @@ -191,6 +200,7 @@ def main(): client_id: {device_id} port: {args.port} topic: {args.topic} + tls: {args.tls} username: {args.username} password: {args.password} @@ -204,6 +214,13 @@ def main(): mqtt_client.username_pw_set(args.username, args.password) mqtt_client.on_connect = on_connect mqtt_client.on_publish = on_publish + + if args.tls is True: + mqtt_client.tls_set(tls_version=ssl.PROTOCOL_TLSv1_2) + + if args.username is not None: + mqtt_client.username_pw_set(args.username, password=args.password) + mqtt_client.connect(args.broker, port=args.port) bus = SMBus(1) From d3c7e731ec6d57f16a3d4983f1cec9529bb37077 Mon Sep 17 00:00:00 2001 From: James Sutton <1068763+jpwsutton@users.noreply.github.com> Date: Fri, 19 Feb 2021 20:23:24 +0000 Subject: [PATCH 12/69] Tweaking the arguments for MQTT TLS Signed-off-by: James Sutton <1068763+jpwsutton@users.noreply.github.com> --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 895a8a26..b725a6c8 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -172,7 +172,7 @@ def main(): parser.add_argument( "--tls", default=DEFAULT_TLS_MODE, - type=bool, + action='store_true', help="enable TLS" ) parser.add_argument( From 27ab20d43a7ebd8722aecf98d86d48b7891b530c Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 31 Aug 2021 17:08:34 +0100 Subject: [PATCH 13/69] Add support for ADS1115 Use the auto-detect feature of the ADS1015 library to support reading the gas sensor via an ADS1115. --- library/enviroplus/gas.py | 8 ++++++-- library/setup.cfg | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/enviroplus/gas.py b/library/enviroplus/gas.py index 584317b6..f5eb2ab6 100644 --- a/library/enviroplus/gas.py +++ b/library/enviroplus/gas.py @@ -41,15 +41,19 @@ def __repr__(self): def setup(): - global adc, _is_setup + global adc, adc_type, _is_setup if _is_setup: return _is_setup = True adc = ads1015.ADS1015(i2c_addr=0x49) + adc_type = adc.detect_chip_type() adc.set_mode('single') adc.set_programmable_gain(MICS6814_GAIN) - adc.set_sample_rate(1600) + if adc_type == 'ADS1115': + adc.set_sample_rate(128) + else: + adc.set_sample_rate(1600) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) diff --git a/library/setup.cfg b/library/setup.cfg index c59250c8..598d88ca 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -32,7 +32,7 @@ install_requires = pms5003 ltr559 st7735 - ads1015 + ads1015 >= 0.0.7 fonts font-roboto astral From 1f68a8eef6b638f91516c19428507a1cf90a67a0 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 2 Sep 2021 11:36:53 +0100 Subject: [PATCH 14/69] Add support for ADS1115 Switch README to markdown (drop dependency on pandoc) --- Makefile | 10 ++-- library/CHANGELOG.txt | 6 +++ library/MANIFEST.in | 2 +- library/README.md | 94 ++++++++++++++++++++++++++++++++++ library/README.rst | 93 --------------------------------- library/enviroplus/__init__.py | 2 +- library/setup.cfg | 5 +- 7 files changed, 110 insertions(+), 102 deletions(-) create mode 100644 library/README.md delete mode 100644 library/README.rst diff --git a/Makefile b/Makefile index d2bba498..948282df 100644 --- a/Makefile +++ b/Makefile @@ -36,14 +36,14 @@ check: tag: git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" -python-readme: library/README.rst +python-readme: library/README.md python-license: library/LICENSE.txt -library/README.rst: README.md library/CHANGELOG.txt - pandoc --from=markdown --to=rst -o library/README.rst README.md - echo "" >> library/README.rst - cat library/CHANGELOG.txt >> library/README.rst +library/README.md: README.md library/CHANGELOG.txt + cp README.md library/README.md + printf "\n# Changelog\n" >> library/README.md + cat library/CHANGELOG.txt >> library/README.md library/LICENSE.txt: LICENSE cp LICENSE library/LICENSE.txt diff --git a/library/CHANGELOG.txt b/library/CHANGELOG.txt index 81d41368..4e59b220 100644 --- a/library/CHANGELOG.txt +++ b/library/CHANGELOG.txt @@ -1,3 +1,9 @@ +0.0.4 +----- + +* Add support for ads1015 >= v0.0.7 (ADS1115 ADCs) +* Packaging tweaks + 0.0.3 ----- diff --git a/library/MANIFEST.in b/library/MANIFEST.in index 43329d99..478b3f30 100644 --- a/library/MANIFEST.in +++ b/library/MANIFEST.in @@ -1,5 +1,5 @@ include CHANGELOG.txt include LICENSE.txt -include README.rst +include README.md include setup.py recursive-include enviroplus *.py diff --git a/library/README.md b/library/README.md new file mode 100644 index 00000000..43572bf1 --- /dev/null +++ b/library/README.md @@ -0,0 +1,94 @@ +# Enviro+ + +Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus + + +[![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/enviroplus-python) +[![Coverage Status](https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/enviroplus-python?branch=master) +[![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) +[![Python Versions](https://img.shields.io/pypi/pyversions/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) + +# Installing + +You're best using the "One-line" install method if you want all of the UART serial configuration for the PMS5003 particulate matter sensor to run automatically. + +**Note** The code in this repository supports both the Enviro+ and Enviro Mini boards. _The Enviro Mini board does not have the Gas sensor or the breakout for the PM sensor._ + +![Enviro Plus pHAT](./Enviro-Plus-pHAT.jpg) +![Enviro Mini pHAT](./Enviro-mini-pHAT.jpg) + +## One-line (Installs from GitHub) + +``` +curl -sSL https://get.pimoroni.com/enviroplus | bash +``` + +**Note** report issues with one-line installer here: https://github.com/pimoroni/get + +## Or... Install and configure dependencies from GitHub: + +* `git clone https://github.com/pimoroni/enviroplus-python` +* `cd enviroplus-python` +* `sudo ./install.sh` + +**Note** Raspbian Lite users may first need to install git: `sudo apt install git` + +## Or... Install from PyPi and configure manually: + +* Run `sudo pip install enviroplus` + +**Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: + +* Enable i2c: `raspi-config nonint do_i2c 0` +* Enable SPI: `raspi-config nonint do_spi 0` + +And if you're using a PMS5003 sensor you will need to: + +* Enable serial: `raspi-config nonint set_config_var enable_uart 1 /boot/config.txt` +* Disable serial terminal: `sudo raspi-config nonint do_serial 1` +* Add `dtoverlay=pi3-miniuart-bt` to your `/boot/config.txt` + +And install additional dependencies: + +``` +sudo apt install python-numpy python-smbus python-pil python-setuptools +``` + +## Alternate Software & User Projects + +* enviro monitor - https://github.com/roscoe81/enviro-monitor +* mqtt-all - https://github.com/robmarkcole/rpi-enviro-mqtt - now upstream: [see examples/mqtt-all.py](examples/mqtt-all.py) +* adafruit_io.py - https://github.com/dedSyn4ps3/enviroplus-python/blob/master/examples/adafruit_io.py - uses Adafruit Blinka and BME280 libraries to publish to Adafruit IO +* enviroplus_exporter - https://github.com/tijmenvandenbrink/enviroplus_exporter - Prometheus exporter (with added support for Luftdaten and InfluxDB Cloud) +* homekit-enviroplus - https://github.com/sighmon/homekit-enviroplus - An Apple HomeKit accessory for the Pimoroni Enviro+ +* go-enviroplus - https://github.com/rubiojr/go-enviroplus - Go modules to read Enviro+ sensors + +## Help & Support + +* GPIO Pinout - https://pinout.xyz/pinout/enviro_plus +* Support forums - http://forums.pimoroni.com/c/support +* Discord - https://discord.gg/hr93ByC + +# Changelog +0.0.4 +----- + +* Add support for ads1015 >= v0.0.7 (ADS1115 ADCs) +* Packaging tweaks + +0.0.3 +----- + +* Fix "self.noise_floor" bug in get_noise_profile + +0.0.2 +----- + +* Add support for extra ADC channel in Gas +* Handle breaking change in new ltr559 library +* Add Noise functionality + +0.0.1 +----- + +* Initial Release diff --git a/library/README.rst b/library/README.rst deleted file mode 100644 index bd74b9d4..00000000 --- a/library/README.rst +++ /dev/null @@ -1,93 +0,0 @@ -Enviro+ -======= - -Designed for environmental monitoring, Enviro+ lets you measure air -quality (pollutant gases and particulates), temperature, pressure, -humidity, light, and noise level. Learn more - -https://shop.pimoroni.com/products/enviro-plus - -|Build Status| |Coverage Status| |PyPi Package| |Python Versions| - -Installing -========== - -You're best using the "One-line" install method if you want all of the -UART serial configuration for the PMS5003 particulate matter sensor to -run automatically. - -One-line (Installs from GitHub) -------------------------------- - -:: - - curl -sSL https://get.pimoroni.com/enviroplus | bash - -**Note** report issues with one-line installer here: -https://github.com/pimoroni/get - -Or... Install and configure dependencies from GitHub: ------------------------------------------------------ - -- ``git clone https://github.com/pimoroni/enviroplus-python`` -- ``cd enviroplus-python`` -- ``sudo ./install.sh`` - -**Note** Raspbian Lite users may first need to install git: -``sudo apt install git`` - -Or... Install from PyPi and configure manually: ------------------------------------------------ - -- Run ``sudo pip install enviroplus`` - -**Note** this wont perform any of the required configuration changes on -your Pi, you may additionally need to: - -- Enable i2c: ``raspi-config nonint do_i2c 0`` -- Enable SPI: ``raspi-config nonint do_spi 0`` - -And if you're using a PMS5003 sensor you will need to: - -- Enable serial: - ``raspi-config nonint set_config_var enable_uart 1 /boot/config.txt`` -- Disable serial terminal: ``sudo raspi-config nonint do_serial 1`` -- Add ``dtoverlay=pi3-miniuart-bt`` to your ``/boot/config.txt`` - -And install additional dependencies: - -:: - - sudo apt install python-numpy python-smbus python-pil python-setuptools - -Help & Support --------------- - -- GPIO Pinout - https://pinout.xyz/pinout/enviro\_plus -- Support forums - http://forums.pimoroni.com/c/support -- Discord - https://discord.gg/hr93ByC - -.. |Build Status| image:: https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master - :target: https://travis-ci.com/pimoroni/enviroplus-python -.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master - :target: https://coveralls.io/github/pimoroni/enviroplus-python?branch=master -.. |PyPi Package| image:: https://img.shields.io/pypi/v/enviroplus.svg - :target: https://pypi.python.org/pypi/enviroplus -.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/enviroplus.svg - :target: https://pypi.python.org/pypi/enviroplus - -0.0.3 ------ - -* Fix "self.noise_floor" bug in get_noise_profile - -0.0.2 ------ - -* Add support for extra ADC channel in Gas -* Handle breaking change in new ltr559 library -* Add Noise functionality - -0.0.1 ------ - -* Initial Release diff --git a/library/enviroplus/__init__.py b/library/enviroplus/__init__.py index ffcc925a..156d6f9a 100644 --- a/library/enviroplus/__init__.py +++ b/library/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = '0.0.3' +__version__ = '0.0.4' diff --git a/library/setup.cfg b/library/setup.cfg index 598d88ca..a6ba0bab 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- [metadata] name = enviroplus -version = 0.0.3 +version = 0.0.4 author = Philip Howard author_email = phil@pimoroni.com description = Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi -long_description = file: README.rst +long_description = file: README.md +long_description_content_type = text/markdown keywords = Raspberry Pi url = https://www.pimoroni.com project_urls = From 582c7757a38492eb1e4d0932490422d936f90eb9 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 2 Nov 2021 10:17:46 +0000 Subject: [PATCH 15/69] Drop Python2 support. --- .github/workflows/test.yml | 4 ++-- README.md | 2 +- install.sh | 11 +++-------- library/CHANGELOG.txt | 5 +++++ library/README.md | 8 +++++++- library/enviroplus/__init__.py | 2 +- library/setup.cfg | 12 ++---------- library/tox.ini | 2 +- 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89154d53..5fac95b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [2.7, 3.5, 3.6, 3.7, 3.8] + python: [3.6, 3.7, 3.9] steps: - uses: actions/checkout@v2 @@ -33,5 +33,5 @@ jobs: run: | python -m pip install coveralls coveralls --service=github - if: ${{ matrix.python == '3.8' }} + if: ${{ matrix.python == '3.9' }} diff --git a/README.md b/README.md index 4bce5f5b..ad90197c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ curl -sSL https://get.pimoroni.com/enviroplus | bash ## Or... Install from PyPi and configure manually: -* Run `sudo pip install enviroplus` +* Run `sudo python3 -m pip install enviroplus` **Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: diff --git a/install.sh b/install.sh index 6837697f..a8513b6d 100755 --- a/install.sh +++ b/install.sh @@ -139,14 +139,6 @@ printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" cd library -printf "Installing for Python 2..\n" -apt_pkg_install "${PY2_DEPS[@]}" -python setup.py install > /dev/null -if [ $? -eq 0 ]; then - success "Done!\n" - echo "pip uninstall $LIBRARY_NAME" >> $UNINSTALLER -fi - if [ -f "/usr/bin/python3" ]; then printf "Installing for Python 3..\n" apt_pkg_install "${PY3_DEPS[@]}" @@ -155,6 +147,9 @@ if [ -f "/usr/bin/python3" ]; then success "Done!\n" echo "pip3 uninstall $LIBRARY_NAME" >> $UNINSTALLER fi +else + printf "/usr/bin/python3 not found. Unable to install!\n" + exit 1 fi cd $WD diff --git a/library/CHANGELOG.txt b/library/CHANGELOG.txt index 4e59b220..4dd54d57 100644 --- a/library/CHANGELOG.txt +++ b/library/CHANGELOG.txt @@ -1,3 +1,8 @@ +0.0.5 +----- + +* Drop Python 2.x support + 0.0.4 ----- diff --git a/library/README.md b/library/README.md index 43572bf1..a1136e8e 100644 --- a/library/README.md +++ b/library/README.md @@ -35,7 +35,7 @@ curl -sSL https://get.pimoroni.com/enviroplus | bash ## Or... Install from PyPi and configure manually: -* Run `sudo pip install enviroplus` +* Run `sudo python3 -m pip install enviroplus` **Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: @@ -70,6 +70,12 @@ sudo apt install python-numpy python-smbus python-pil python-setuptools * Discord - https://discord.gg/hr93ByC # Changelog + +0.0.5 +----- + +* Drop Python 2.x support + 0.0.4 ----- diff --git a/library/enviroplus/__init__.py b/library/enviroplus/__init__.py index 156d6f9a..eead3198 100644 --- a/library/enviroplus/__init__.py +++ b/library/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = '0.0.4' +__version__ = '0.0.5' diff --git a/library/setup.cfg b/library/setup.cfg index a6ba0bab..591de903 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- [metadata] name = enviroplus -version = 0.0.4 +version = 0.0.5 author = Philip Howard author_email = phil@pimoroni.com description = Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi @@ -20,13 +20,13 @@ classifiers = Operating System :: POSIX :: Linux License :: OSI Approved :: MIT License Intended Audience :: Developers - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Topic :: Software Development Topic :: Software Development :: Libraries Topic :: System :: Hardware [options] +python_requires = >= 3.6 packages = enviroplus install_requires = pimoroni-bme280 @@ -54,14 +54,6 @@ ignore = [pimoroni] py2deps = - python-pip - python-numpy - python-smbus - python-pil - python-cffi - python-spidev - python-rpi.gpio - libportaudio2 py3deps = python3-pip python3-numpy diff --git a/library/tox.ini b/library/tox.ini index aa962163..fcee0794 100644 --- a/library/tox.ini +++ b/library/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,35},qa +envlist = py{36, 37, 38, 39},qa skip_missing_interpreters = True [testenv] From 47089ae867ac6aa9b4a3f23a02f10631fcd10aa5 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 2 Nov 2021 10:40:37 +0000 Subject: [PATCH 16/69] Linting fixes --- examples/luftdaten.py | 4 ++-- examples/mqtt-all.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/luftdaten.py b/examples/luftdaten.py index 0c13771b..d0bee376 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -127,7 +127,7 @@ def send_to_luftdaten(values, id): resp_pm = None resp_bmp = None - + try: resp_pm = requests.post( "https://api.luftdaten.info/v1/push-sensor-data/", @@ -150,7 +150,7 @@ def send_to_luftdaten(values, id): except requests.exceptions.RequestException as e: logging.warning('Luftdaten PM Request Error: {}'.format(e)) - try: + try: resp_bmp = requests.post( "https://api.luftdaten.info/v1/push-sensor-data/", json={ diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index b725a6c8..8220c678 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -1,9 +1,9 @@ +#!/usr/bin/env python3 """ Run mqtt broker on localhost: sudo apt-get install mosquitto mosquitto-clients Example run: python3 mqtt-all.py --broker 192.168.1.164 --topic enviro --username xxx --password xxxx """ -#!/usr/bin/env python3 import argparse import ST7735 @@ -43,6 +43,7 @@ DEFAULT_USERNAME = None DEFAULT_PASSWORD = None + # mqtt callbacks def on_connect(client, userdata, flags, rc): if rc == 0: @@ -99,7 +100,7 @@ def get_cpu_temperature(): ["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True ) output, _error = process.communicate() - return float(output[output.index("=") + 1 : output.rindex("'")]) + return float(output[output.index("=") + 1:output.rindex("'")]) # Get Raspberry Pi serial number to use as ID @@ -240,7 +241,7 @@ def main(): HAS_PMS = False try: pms5003 = PMS5003() - pm_values = pms5003.read() + _ = pms5003.read() HAS_PMS = True print("PMS5003 sensor is connected") except SerialTimeoutError: From 7f40ecfda25117e585f1e95a51e4af65a679b433 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 18 Jun 2020 10:42:25 +0100 Subject: [PATCH 17/69] Add available() method to gas sensor This change catches an IOError when setting up the gas sensor and provides an `available()` method for determining if a sensor is present. --- library/enviroplus/gas.py | 26 +++-- library/tests/conftest.py | 189 +++++++++++++++++++----------------- library/tests/test_noise.py | 96 +++++++++--------- library/tests/test_setup.py | 13 +++ 4 files changed, 179 insertions(+), 145 deletions(-) diff --git a/library/enviroplus/gas.py b/library/enviroplus/gas.py index f5eb2ab6..54c240f6 100644 --- a/library/enviroplus/gas.py +++ b/library/enviroplus/gas.py @@ -10,6 +10,7 @@ ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE _is_setup = False +_is_available = False _adc_enabled = False _adc_gain = 6.148 @@ -41,13 +42,19 @@ def __repr__(self): def setup(): - global adc, adc_type, _is_setup + global adc, adc_type, _is_setup, _is_available if _is_setup: return _is_setup = True - adc = ads1015.ADS1015(i2c_addr=0x49) - adc_type = adc.detect_chip_type() + try: + adc = ads1015.ADS1015(i2c_addr=0x49) + adc_type = adc.detect_chip_type() + _is_available = True + except IOError: + _is_available = False + return + adc.set_mode('single') adc.set_programmable_gain(MICS6814_GAIN) if adc_type == 'ADS1115': @@ -62,6 +69,11 @@ def setup(): atexit.register(cleanup) +def available(): + setup() + return _is_available + + def enable_adc(value=True): """Enable reading from the additional ADC pin.""" global _adc_enabled @@ -81,6 +93,10 @@ def cleanup(): def read_all(): """Return gas resistence for oxidising, reducing and NH3""" setup() + + if not _is_available: + raise RuntimeError("Gas sensor not connected.") + ox = adc.get_voltage('in0/gnd') red = adc.get_voltage('in1/gnd') nh3 = adc.get_voltage('in2/gnd') @@ -119,7 +135,6 @@ def read_oxidising(): Eg chlorine, nitrous oxide """ - setup() return read_all().oxidising @@ -128,17 +143,14 @@ def read_reducing(): Eg hydrogen, carbon monoxide """ - setup() return read_all().reducing def read_nh3(): """Return gas resistance for nh3/ammonia""" - setup() return read_all().nh3 def read_adc(): """Return spare ADC channel value""" - setup() return read_all().adc diff --git a/library/tests/conftest.py b/library/tests/conftest.py index 8a5c54c5..d076a6c3 100644 --- a/library/tests/conftest.py +++ b/library/tests/conftest.py @@ -1,90 +1,99 @@ -"""Test configuration. -These allow the mocking of various Python modules -that might otherwise have runtime side-effects. -""" -import sys -import mock -import pytest -from i2cdevice import MockSMBus - - -class SMBusFakeDevice(MockSMBus): - def __init__(self, i2c_bus): - MockSMBus.__init__(self, i2c_bus) - self.regs[0x00:0x01] = 0x0f, 0x00 - - -@pytest.fixture(scope='function', autouse=True) -def cleanup(): - yield None - try: - del sys.modules['enviroplus'] - except KeyError: - pass - try: - del sys.modules['enviroplus.noise'] - except KeyError: - pass - try: - del sys.modules['enviroplus.gas'] - except KeyError: - pass - - -@pytest.fixture(scope='function', autouse=False) -def GPIO(): - """Mock RPi.GPIO module.""" - GPIO = mock.MagicMock() - # Fudge for Python < 37 (possibly earlier) - sys.modules['RPi'] = mock.Mock() - sys.modules['RPi'].GPIO = GPIO - sys.modules['RPi.GPIO'] = GPIO - yield GPIO - del sys.modules['RPi'] - del sys.modules['RPi.GPIO'] - - -@pytest.fixture(scope='function', autouse=False) -def spidev(): - """Mock spidev module.""" - spidev = mock.MagicMock() - sys.modules['spidev'] = spidev - yield spidev - del sys.modules['spidev'] - - -@pytest.fixture(scope='function', autouse=False) -def smbus(): - """Mock smbus module.""" - smbus = mock.MagicMock() - smbus.SMBus = SMBusFakeDevice - sys.modules['smbus'] = smbus - yield smbus - del sys.modules['smbus'] - - -@pytest.fixture(scope='function', autouse=False) -def atexit(): - """Mock atexit module.""" - atexit = mock.MagicMock() - sys.modules['atexit'] = atexit - yield atexit - del sys.modules['atexit'] - - -@pytest.fixture(scope='function', autouse=False) -def sounddevice(): - """Mock sounddevice module.""" - sounddevice = mock.MagicMock() - sys.modules['sounddevice'] = sounddevice - yield sounddevice - del sys.modules['sounddevice'] - - -@pytest.fixture(scope='function', autouse=False) -def numpy(): - """Mock numpy module.""" - numpy = mock.MagicMock() - sys.modules['numpy'] = numpy - yield numpy - del sys.modules['numpy'] +"""Test configuration. +These allow the mocking of various Python modules +that might otherwise have runtime side-effects. +""" +import sys +import mock +import pytest +from i2cdevice import MockSMBus + + +class SMBusFakeDevice(MockSMBus): + def __init__(self, i2c_bus): + MockSMBus.__init__(self, i2c_bus) + self.regs[0x00:0x01] = 0x0f, 0x00 + + +@pytest.fixture(scope='function', autouse=True) +def cleanup(): + yield None + try: + del sys.modules['enviroplus'] + except KeyError: + pass + try: + del sys.modules['enviroplus.noise'] + except KeyError: + pass + try: + del sys.modules['enviroplus.gas'] + except KeyError: + pass + + +@pytest.fixture(scope='function', autouse=False) +def GPIO(): + """Mock RPi.GPIO module.""" + GPIO = mock.MagicMock() + # Fudge for Python < 37 (possibly earlier) + sys.modules['RPi'] = mock.Mock() + sys.modules['RPi'].GPIO = GPIO + sys.modules['RPi.GPIO'] = GPIO + yield GPIO + del sys.modules['RPi'] + del sys.modules['RPi.GPIO'] + + +@pytest.fixture(scope='function', autouse=False) +def spidev(): + """Mock spidev module.""" + spidev = mock.MagicMock() + sys.modules['spidev'] = spidev + yield spidev + del sys.modules['spidev'] + + +@pytest.fixture(scope='function', autouse=False) +def smbus(): + """Mock smbus module.""" + smbus = mock.MagicMock() + smbus.SMBus = SMBusFakeDevice + sys.modules['smbus'] = smbus + yield smbus + del sys.modules['smbus'] + + +@pytest.fixture(scope='function', autouse=False) +def mocksmbus(): + """Mock smbus module.""" + smbus = mock.MagicMock() + sys.modules['smbus'] = smbus + yield smbus + del sys.modules['smbus'] + + +@pytest.fixture(scope='function', autouse=False) +def atexit(): + """Mock atexit module.""" + atexit = mock.MagicMock() + sys.modules['atexit'] = atexit + yield atexit + del sys.modules['atexit'] + + +@pytest.fixture(scope='function', autouse=False) +def sounddevice(): + """Mock sounddevice module.""" + sounddevice = mock.MagicMock() + sys.modules['sounddevice'] = sounddevice + yield sounddevice + del sys.modules['sounddevice'] + + +@pytest.fixture(scope='function', autouse=False) +def numpy(): + """Mock numpy module.""" + numpy = mock.MagicMock() + sys.modules['numpy'] = numpy + yield numpy + del sys.modules['numpy'] diff --git a/library/tests/test_noise.py b/library/tests/test_noise.py index 3778c166..75aa89c2 100644 --- a/library/tests/test_noise.py +++ b/library/tests/test_noise.py @@ -1,48 +1,48 @@ -import pytest - - -def test_noise_setup(sounddevice, numpy): - from enviroplus.noise import Noise - - noise = Noise(sample_rate=16000, duration=0.1) - del noise - - -def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy): - from enviroplus.noise import Noise - - noise = Noise(sample_rate=16000, duration=0.1) - noise.get_amplitudes_at_frequency_ranges([ - (100, 500), - (501, 1000) - ]) - - sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') - - -def test_noise_get_noise_profile(sounddevice, numpy): - from enviroplus.noise import Noise - - numpy.mean.return_value = 10.0 - - noise = Noise(sample_rate=16000, duration=0.1) - amp_low, amp_mid, amp_high, amp_total = noise.get_noise_profile( - noise_floor=100, - low=0.12, - mid=0.36, - high=None) - - sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') - - assert amp_total == 10.0 - - -def test_get_amplitude_at_frequency_range(sounddevice, numpy): - from enviroplus.noise import Noise - - noise = Noise(sample_rate=16000, duration=0.1) - - noise.get_amplitude_at_frequency_range(0, 8000) - - with pytest.raises(ValueError): - noise.get_amplitude_at_frequency_range(0, 16000) +import pytest + + +def test_noise_setup(sounddevice, numpy): + from enviroplus.noise import Noise + + noise = Noise(sample_rate=16000, duration=0.1) + del noise + + +def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy): + from enviroplus.noise import Noise + + noise = Noise(sample_rate=16000, duration=0.1) + noise.get_amplitudes_at_frequency_ranges([ + (100, 500), + (501, 1000) + ]) + + sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') + + +def test_noise_get_noise_profile(sounddevice, numpy): + from enviroplus.noise import Noise + + numpy.mean.return_value = 10.0 + + noise = Noise(sample_rate=16000, duration=0.1) + amp_low, amp_mid, amp_high, amp_total = noise.get_noise_profile( + noise_floor=100, + low=0.12, + mid=0.36, + high=None) + + sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') + + assert amp_total == 10.0 + + +def test_get_amplitude_at_frequency_range(sounddevice, numpy): + from enviroplus.noise import Noise + + noise = Noise(sample_rate=16000, duration=0.1) + + noise.get_amplitude_at_frequency_range(0, 8000) + + with pytest.raises(ValueError): + noise.get_amplitude_at_frequency_range(0, 16000) diff --git a/library/tests/test_setup.py b/library/tests/test_setup.py index 2aa7b492..89d3a7bd 100644 --- a/library/tests/test_setup.py +++ b/library/tests/test_setup.py @@ -5,6 +5,19 @@ def test_gas_setup(GPIO, smbus): gas.setup() +def test_gas_unavailable(GPIO, mocksmbus): + from enviroplus import gas + mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh noes!") + gas._is_setup = False + assert gas.available() == False + + +def test_gas_available(GPIO, mocksmbus): + from enviroplus import gas + gas._is_setup = False + assert gas.available() == True + + def test_gas_read_all(GPIO, smbus): from enviroplus import gas gas._is_setup = False From 18f582c6d956eb39371e65488d0f3277ef8357b5 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 14 Nov 2020 17:42:49 +0000 Subject: [PATCH 18/69] Trigger tests From be638668c8c917947754b0a6af9224cc59aad6e4 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 14 Nov 2020 17:47:39 +0000 Subject: [PATCH 19/69] Test read_all throws exception when unavailable --- library/tests/test_setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/tests/test_setup.py b/library/tests/test_setup.py index 89d3a7bd..162f1bce 100644 --- a/library/tests/test_setup.py +++ b/library/tests/test_setup.py @@ -1,3 +1,6 @@ +import pytest + + def test_gas_setup(GPIO, smbus): from enviroplus import gas gas._is_setup = False @@ -11,6 +14,9 @@ def test_gas_unavailable(GPIO, mocksmbus): gas._is_setup = False assert gas.available() == False + with pytest.raises(RuntimeError): + gas.read_all() + def test_gas_available(GPIO, mocksmbus): from enviroplus import gas From e3df80ec28c41d1e8e0c996442c86642e970fcbe Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 2 Nov 2021 11:04:33 +0000 Subject: [PATCH 20/69] Fixup tests for ADC detect. --- library/tests/conftest.py | 16 ++++++++++++++++ library/tests/test_setup.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/library/tests/conftest.py b/library/tests/conftest.py index d076a6c3..b3fa3766 100644 --- a/library/tests/conftest.py +++ b/library/tests/conftest.py @@ -14,6 +14,12 @@ def __init__(self, i2c_bus): self.regs[0x00:0x01] = 0x0f, 0x00 +class SMBusFakeDeviceNoTimeout(MockSMBus): + def __init__(self, i2c_bus): + MockSMBus.__init__(self, i2c_bus) + self.regs[0x00:0x01] = 0x0f, 0x80 + + @pytest.fixture(scope='function', autouse=True) def cleanup(): yield None @@ -63,6 +69,16 @@ def smbus(): del sys.modules['smbus'] +@pytest.fixture(scope='function', autouse=False) +def smbus_notimeout(): + """Mock smbus module.""" + smbus = mock.MagicMock() + smbus.SMBus = SMBusFakeDeviceNoTimeout + sys.modules['smbus'] = smbus + yield smbus + del sys.modules['smbus'] + + @pytest.fixture(scope='function', autouse=False) def mocksmbus(): """Mock smbus module.""" diff --git a/library/tests/test_setup.py b/library/tests/test_setup.py index 162f1bce..40bf80d6 100644 --- a/library/tests/test_setup.py +++ b/library/tests/test_setup.py @@ -18,7 +18,7 @@ def test_gas_unavailable(GPIO, mocksmbus): gas.read_all() -def test_gas_available(GPIO, mocksmbus): +def test_gas_available(GPIO, smbus_notimeout): from enviroplus import gas gas._is_setup = False assert gas.available() == True From ac66a840b3d4a3e6ee04354e6c758e44386ab31e Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 2 Nov 2021 11:11:03 +0000 Subject: [PATCH 21/69] Update changelog & readme --- library/CHANGELOG.txt | 1 + library/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/library/CHANGELOG.txt b/library/CHANGELOG.txt index 4dd54d57..d5d7bc88 100644 --- a/library/CHANGELOG.txt +++ b/library/CHANGELOG.txt @@ -2,6 +2,7 @@ ----- * Drop Python 2.x support +* Add "available()" method for gas sensor 0.0.4 ----- diff --git a/library/README.md b/library/README.md index a1136e8e..f637ae85 100644 --- a/library/README.md +++ b/library/README.md @@ -75,6 +75,7 @@ sudo apt install python-numpy python-smbus python-pil python-setuptools ----- * Drop Python 2.x support +* Add "available()" method for gas sensor 0.0.4 ----- From 05c735acbc7d40124630f375a83fe6d32c53861f Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Tue, 2 Nov 2021 11:13:01 +0000 Subject: [PATCH 22/69] Drop Python2 from Makefile --- Makefile | 3 +-- README.md | 2 +- library/README.md | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 948282df..12afc903 100644 --- a/Makefile +++ b/Makefile @@ -50,10 +50,9 @@ library/LICENSE.txt: LICENSE python-wheels: python-readme python-license cd library; python3 setup.py bdist_wheel - cd library; python setup.py bdist_wheel python-sdist: python-readme python-license - cd library; python setup.py sdist + cd library; python3 setup.py sdist python-clean: -rm -r library/dist diff --git a/README.md b/README.md index ad90197c..f6b0d628 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Enviro+ +# Enviro+ Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus diff --git a/library/README.md b/library/README.md index f637ae85..67360fda 100644 --- a/library/README.md +++ b/library/README.md @@ -1,4 +1,4 @@ -# Enviro+ +# Enviro+ Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus @@ -70,7 +70,6 @@ sudo apt install python-numpy python-smbus python-pil python-setuptools * Discord - https://discord.gg/hr93ByC # Changelog - 0.0.5 ----- From 01f6f2772e4557d71e3b2232eddf580080e687c1 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Thu, 4 Nov 2021 10:22:57 +0000 Subject: [PATCH 23/69] Correct README to python3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6b0d628..9c32ab56 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ And if you're using a PMS5003 sensor you will need to: And install additional dependencies: ``` -sudo apt install python-numpy python-smbus python-pil python-setuptools +sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools ``` ## Alternate Software & User Projects From 3afa06d1db501c7c9ae9046b96bee198e35f1ad0 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Thu, 4 Nov 2021 11:08:58 +0000 Subject: [PATCH 24/69] Direct users toward Python3, install explicitly --- README.md | 2 ++ install.sh | 3 ++- library/setup.cfg | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c32ab56..76054ecb 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ You're best using the "One-line" install method if you want all of the UART seri ![Enviro Plus pHAT](./Enviro-Plus-pHAT.jpg) ![Enviro Mini pHAT](./Enviro-mini-pHAT.jpg) +:warning: This library now supports Python 3 only, Python 2 is EOL - https://www.python.org/doc/sunset-python-2/ + ## One-line (Installs from GitHub) ``` diff --git a/install.sh b/install.sh index a8513b6d..60f2a19d 100755 --- a/install.sh +++ b/install.sh @@ -184,5 +184,6 @@ if [ -d "examples" ]; then fi success "\nAll done!" -inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" +warning "If this is your first time installing you should --reboot-- for hardware changes to take effect.\n" +warning "This library is installed for Python 3 *only* make sure to use \"python3\" when running examples.\n" inform "Find uninstall steps in $UNINSTALLER\n" diff --git a/library/setup.cfg b/library/setup.cfg index 591de903..a5fcae51 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -55,6 +55,7 @@ ignore = [pimoroni] py2deps = py3deps = + python3 python3-pip python3-numpy python3-smbus From ffb7a0334f2a814ecc3d38f34b9070dc1991e5d9 Mon Sep 17 00:00:00 2001 From: Philip Howard Date: Thu, 4 Nov 2021 11:43:51 +0000 Subject: [PATCH 25/69] Add check-install.py debug script --- check-install.py | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 check-install.py diff --git a/check-install.py b/check-install.py new file mode 100755 index 00000000..ea5eecd6 --- /dev/null +++ b/check-install.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +f"Sorry! This program requires Python >= 3.6 😅. Run with \"python3 check-install.py\"" + +CONFIG_FILE = "/boot/config.txt" + +print("""Checking Enviro+ install, please wait...""") + +errors = 0 +check_apt = False + +try: + import apt + check_apt = True +except ImportErorr: + print("⚠️ Could not import \"apt\". Unable to verify system dependencies.") + + +apt_deps = { + "python3", + "python3-pip", + "python3-numpy", + "python3-smbus", + "python3-pil", + "python3-cffi", + "python3-spidev", + "python3-rpi.gpio", + "libportaudio2" +} + +deps = { + "bme280": None, + "pms5003": None, + "ltr559": None, + "ST7735": None, + "ads1015": "0.0.7", + "fonts": None, + "font_roboto": None, + "astral": None, + "pytz": None, + "sounddevice": None, + "paho.mqtt": None +} + +config = { + "dtparam=i2c_arm=on", + "dtparam=spi=on", + "dtoverlay=adau7002-simple", + "dtoverlay=pi3-miniuart-bt", + "enable_uart=1" +} + +if check_apt: + print("\nSystem dependencies...") + print(" Retrieving cache...") + cache = apt.Cache() + + for dep in apt_deps: + installed = False + print(f" Checking for {dep}".ljust(35), end="") + try: + installed = cache[dep].is_installed + except KeyError: + pass + + if installed: + print("✅") + else: + print("⚠️ Missing!") + errors += 1 + +print("\nPython dependencies...") + +for dep, version in deps.items(): + print(f" Checking for {dep}".ljust(35), end="") + try: + __import__(dep) + print("✅") + except ImportError: + print("⚠️ Missing!") + errors += 1 + +print("\nSystem config...") + +config_txt = open(CONFIG_FILE, "r").read().split("\n") + +def check_config(line): + global errors + print(f" Checking for {line} in {CONFIG_FILE}: ", end="") + for cline in config_txt: + if cline.startswith(line): + print("✅") + return + print("⚠️ Missing!") + errors += 1 + +for line in config: + check_config(line) + +if errors > 0: + print("\n⚠️ Config errors were found! Something might be awry.") +else: + print("\n✅ Looks good from here!") + +print("\nHave you?") +print(" • Rebooted after installing") +print(" • Made sure to run examples with \"python3\"") +print(" • Checked for any errors when running \"sudo ./install.sh\"") From fbcd83aa555fec60ad21290e4c39e72d121e6297 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 8 Nov 2021 12:09:37 +0000 Subject: [PATCH 26/69] Add Bullseye installer --- install-bullseye.sh | 254 ++++++++++++++++++++++++++++++++++++++++++++ install.sh | 41 ++++++- 2 files changed, 294 insertions(+), 1 deletion(-) create mode 100755 install-bullseye.sh diff --git a/install-bullseye.sh b/install-bullseye.sh new file mode 100755 index 00000000..016c7ee9 --- /dev/null +++ b/install-bullseye.sh @@ -0,0 +1,254 @@ +#!/bin/bash +CONFIG=/boot/config.txt +DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` +CONFIG_BACKUP=false +APT_HAS_UPDATED=false +USER_HOME=/home/$SUDO_USER +RESOURCES_TOP_DIR=$USER_HOME/Pimoroni +WD=`pwd` +USAGE="sudo ./install.sh (--unstable)" +POSITIONAL_ARGS=() +UNSTABLE=false +PYTHON="/usr/bin/python3" +CODENAME=`lsb_release -sc` + +distro_check() { + if [[ $CODENAME != "bullseye" ]]; then + printf "This installer is for Raspberry Pi OS: Bullseye only, current distro: $CODENAME\n" + exit 1 + fi +} + +user_check() { + if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root. Try 'sudo ./install.sh'\n" + exit 1 + fi +} + +confirm() { + if [ "$FORCE" == '-y' ]; then + true + else + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi + fi +} + +prompt() { + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi +} + +success() { + echo -e "$(tput setaf 2)$1$(tput sgr0)" +} + +inform() { + echo -e "$(tput setaf 6)$1$(tput sgr0)" +} + +warning() { + echo -e "$(tput setaf 1)$1$(tput sgr0)" +} + +function do_config_backup { + if [ ! $CONFIG_BACKUP == true ]; then + CONFIG_BACKUP=true + FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" + inform "Backing up $CONFIG to /boot/$FILENAME\n" + cp $CONFIG /boot/$FILENAME + mkdir -p $RESOURCES_TOP_DIR/config-backups/ + cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME + if [ -f "$UNINSTALLER" ]; then + echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER + fi + fi +} + +function apt_pkg_install { + PACKAGES=() + PACKAGES_IN=("$@") + for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do + PACKAGE="${PACKAGES_IN[$i]}" + if [ "$PACKAGE" == "" ]; then continue; fi + printf "Checking for $PACKAGE\n" + dpkg -L $PACKAGE > /dev/null 2>&1 + if [ "$?" == "1" ]; then + PACKAGES+=("$PACKAGE") + fi + done + PACKAGES="${PACKAGES[@]}" + if ! [ "$PACKAGES" == "" ]; then + echo "Installing missing packages: $PACKAGES" + if [ ! $APT_HAS_UPDATED ]; then + apt update + APT_HAS_UPDATED=true + fi + apt install -y $PACKAGES + if [ -f "$UNINSTALLER" ]; then + echo "apt uninstall -y $PACKAGES" + fi + fi +} + +while [[ $# -gt 0 ]]; do + K="$1" + case $K in + -u|--unstable) + UNSTABLE=true + shift + ;; + -p|--python) + PYTHON=$2 + shift + shift + ;; + *) + if [[ $1 == -* ]]; then + printf "Unrecognised option: $1\n"; + printf "Usage: $USAGE\n"; + exit 1 + fi + POSITIONAL_ARGS+=("$1") + shift + esac +done + +distro_check +user_check + +if [ ! -f "$PYTHON" ]; then + printf "Python path $PYTHON not found!\n" + exit 1 +fi + +PYTHON_VER=`$PYTHON --version` + +inform "Installing. Please wait..." + +$PYTHON -m pip install --upgrade configparser + +CONFIG_VARS=`$PYTHON - < $UNINSTALLER +printf "It's recommended you run these steps manually.\n" +printf "If you want to run the full script, open it in\n" +printf "an editor and remove 'exit 1' from below.\n" +exit 1 +EOF + +printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" + +if $UNSTABLE; then + warning "Installing unstable library from source.\n\n" +else + printf "Installing stable library from pypi.\n\n" +fi + +cd library + +printf "Installing for $PYTHON_VER...\n" +apt_pkg_install "${PY3_DEPS[@]}" +if $UNSTABLE; then + $PYTHON setup.py install > /dev/null +else + $PYTHON -m pip install --upgrade $LIBRARY_NAME +fi +if [ $? -eq 0 ]; then + success "Done!\n" + echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER +fi + +cd $WD + +for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do + CMD="${SETUP_CMDS[$i]}" + # Attempt to catch anything that touches /boot/config.txt and trigger a backup + if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then + do_config_backup + fi + eval $CMD +done + +for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do + CONFIG_LINE="${CONFIG_TXT[$i]}" + if ! [ "$CONFIG_LINE" == "" ]; then + do_config_backup + inform "Adding $CONFIG_LINE to $CONFIG\n" + sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG + if ! grep -q "^$CONFIG_LINE" $CONFIG; then + printf "$CONFIG_LINE\n" >> $CONFIG + fi + fi +done + +if [ -d "examples" ]; then + if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then + inform "Copying examples to $RESOURCES_DIR" + cp -r examples/ $RESOURCES_DIR + echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER + success "Done!" + fi +fi + +printf "\n" + +if [ -f "/usr/bin/pydoc" ]; then + printf "Generating documentation.\n" + pydoc -w $LIBRARY_NAME > /dev/null + if [ -f "$LIBRARY_NAME.html" ]; then + cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html + rm -f $LIBRARY_NAME.html + inform "Documentation saved to $RESOURCES_DIR/docs.html" + success "Done!" + else + warning "Error: Failed to generate documentation." + fi +fi + +success "\nAll done!" +warning "If this is your first time installing you should --reboot-- for hardware changes to take effect.\n" +warning "This library is installed for python 3 *only* make sure to use \"python3\" when running examples.\n" diff --git a/install.sh b/install.sh index 60f2a19d..baa9d5af 100755 --- a/install.sh +++ b/install.sh @@ -7,6 +7,15 @@ APT_HAS_UPDATED=false USER_HOME=/home/$SUDO_USER RESOURCES_TOP_DIR=$USER_HOME/Pimoroni WD=`pwd` +USAGE="sudo ./install.sh (--unstable)" +POSITIONAL_ARGS=() +UNSTABLE=false +CODENAME=`lsb_release -sc` + +if [[ $CODENAME == "bullseye" ]]; then + bash ./install-bullseye.sh + exit $? +fi user_check() { if [ $(id -u) -ne 0 ]; then @@ -68,6 +77,7 @@ function apt_pkg_install { PACKAGES_IN=("$@") for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do PACKAGE="${PACKAGES_IN[$i]}" + if [ "$PACKAGE" == "" ]; then continue; fi printf "Checking for $PACKAGE\n" dpkg -L $PACKAGE > /dev/null 2>&1 if [ "$?" == "1" ]; then @@ -88,6 +98,24 @@ function apt_pkg_install { fi } +while [[ $# -gt 0 ]]; do + K="$1" + case $K in + -u|--unstable) + UNSTABLE=true + shift + ;; + *) + if [[ $1 == -* ]]; then + printf "Unrecognised option: $1\n"; + printf "Usage: $USAGE\n"; + exit 1 + fi + POSITIONAL_ARGS+=("$1") + shift + esac +done + user_check apt_pkg_install python-configparser @@ -137,12 +165,22 @@ EOF printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" +if $UNSTABLE; then + warning "Installing unstable library from source.\n\n" +else + printf "Installing stable library from pypi.\n\n" +fi + cd library if [ -f "/usr/bin/python3" ]; then printf "Installing for Python 3..\n" apt_pkg_install "${PY3_DEPS[@]}" - python3 setup.py install > /dev/null + if $UNSTABLE; then + python3 setup.py install > /dev/null + else + pip3 install --upgrade $LIBRARY_NAME + fi if [ $? -eq 0 ]; then success "Done!\n" echo "pip3 uninstall $LIBRARY_NAME" >> $UNINSTALLER @@ -180,6 +218,7 @@ if [ -d "examples" ]; then inform "Copying examples to $RESOURCES_DIR" cp -r examples/ $RESOURCES_DIR echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER + success "Done!" fi fi From 40edd6b13ea21f2d93d2148f14dc64f6ff954cdd Mon Sep 17 00:00:00 2001 From: Rajko Zschiegner Date: Sun, 6 Feb 2022 09:52:42 +0100 Subject: [PATCH 27/69] Name change of Luftdaten.info to Sensor.Community API endpoints changed to reflect name change of Luftdaten.info to Sensor.Community --- examples/luftdaten.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/luftdaten.py b/examples/luftdaten.py index d0bee376..7b6c43bb 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -130,7 +130,7 @@ def send_to_luftdaten(values, id): try: resp_pm = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", + "https://api.sensor.community/v1/push-sensor-data/", json={ "software_version": "enviro-plus 0.0.1", "sensordatavalues": pm_values_json @@ -144,15 +144,15 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning('Luftdaten PM Connection Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) PM Connection Error: {}'.format(e)) except requests.exceptions.Timeout as e: - logging.warning('Luftdaten PM Timeout Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) PM Timeout Error: {}'.format(e)) except requests.exceptions.RequestException as e: - logging.warning('Luftdaten PM Request Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) PM Request Error: {}'.format(e)) try: resp_bmp = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", + "https://api.sensor.community/v1/push-sensor-data/", json={ "software_version": "enviro-plus 0.0.1", "sensordatavalues": temp_values_json @@ -166,11 +166,11 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning('Luftdaten Climate Connection Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) Climate Connection Error: {}'.format(e)) except requests.exceptions.Timeout as e: - logging.warning('Luftdaten Climate Timeout Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) Climate Timeout Error: {}'.format(e)) except requests.exceptions.RequestException as e: - logging.warning('Luftdaten Climate Request Error: {}'.format(e)) + logging.warning('Sensor.Community (Luftdaten) Climate Request Error: {}'.format(e)) if resp_pm is not None and resp_bmp is not None: if resp_pm.ok and resp_bmp.ok: From bb38109e947e4fbce5b3993101a2952f1ea19bfc Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 9 Feb 2022 18:56:38 +0000 Subject: [PATCH 28/69] Pass --unstable to bullseye installer --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index baa9d5af..65f33422 100755 --- a/install.sh +++ b/install.sh @@ -13,7 +13,7 @@ UNSTABLE=false CODENAME=`lsb_release -sc` if [[ $CODENAME == "bullseye" ]]; then - bash ./install-bullseye.sh + bash ./install-bullseye.sh $@ exit $? fi From 599615d0aca427acfca081a303f60d8acdfe3e34 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 11 Feb 2022 16:53:14 +0000 Subject: [PATCH 29/69] Fix audio capture for #91 --- library/enviroplus/noise.py | 1 + library/tests/test_noise.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/enviroplus/noise.py b/library/enviroplus/noise.py index 7b6d5e28..0d413bde 100644 --- a/library/enviroplus/noise.py +++ b/library/enviroplus/noise.py @@ -83,6 +83,7 @@ def get_noise_profile(self, def _record(self): return sounddevice.rec( int(self.duration * self.sample_rate), + device='adau7002', samplerate=self.sample_rate, blocking=True, channels=1, diff --git a/library/tests/test_noise.py b/library/tests/test_noise.py index 75aa89c2..4949a3d3 100644 --- a/library/tests/test_noise.py +++ b/library/tests/test_noise.py @@ -17,7 +17,7 @@ def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy): (501, 1000) ]) - sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') + sounddevice.rec.assert_called_with(0.1 * 16000, device='adau7002', samplerate=16000, blocking=True, channels=1, dtype='float64') def test_noise_get_noise_profile(sounddevice, numpy): @@ -32,7 +32,7 @@ def test_noise_get_noise_profile(sounddevice, numpy): mid=0.36, high=None) - sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64') + sounddevice.rec.assert_called_with(0.1 * 16000, device='adau7002', samplerate=16000, blocking=True, channels=1, dtype='float64') assert amp_total == 10.0 From 78b620a209ee0f5ded4e002e50745c235639f2c0 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 3 Mar 2022 13:00:10 +0000 Subject: [PATCH 30/69] Prep for v0.0.6 --- library/CHANGELOG.txt | 5 +++++ library/README.md | 9 ++++++++- library/enviroplus/__init__.py | 2 +- library/setup.cfg | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/CHANGELOG.txt b/library/CHANGELOG.txt index d5d7bc88..21f175aa 100644 --- a/library/CHANGELOG.txt +++ b/library/CHANGELOG.txt @@ -1,3 +1,8 @@ +0.0.6 +----- + +* Fix noise by specifying adau7002 device + 0.0.5 ----- diff --git a/library/README.md b/library/README.md index 67360fda..14988143 100644 --- a/library/README.md +++ b/library/README.md @@ -17,6 +17,8 @@ You're best using the "One-line" install method if you want all of the UART seri ![Enviro Plus pHAT](./Enviro-Plus-pHAT.jpg) ![Enviro Mini pHAT](./Enviro-mini-pHAT.jpg) +:warning: This library now supports Python 3 only, Python 2 is EOL - https://www.python.org/doc/sunset-python-2/ + ## One-line (Installs from GitHub) ``` @@ -51,7 +53,7 @@ And if you're using a PMS5003 sensor you will need to: And install additional dependencies: ``` -sudo apt install python-numpy python-smbus python-pil python-setuptools +sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools ``` ## Alternate Software & User Projects @@ -70,6 +72,11 @@ sudo apt install python-numpy python-smbus python-pil python-setuptools * Discord - https://discord.gg/hr93ByC # Changelog +0.0.6 +----- + +* Fix noise by specifying adau7002 device + 0.0.5 ----- diff --git a/library/enviroplus/__init__.py b/library/enviroplus/__init__.py index eead3198..fa9c4ec2 100644 --- a/library/enviroplus/__init__.py +++ b/library/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = '0.0.5' +__version__ = '0.0.6' diff --git a/library/setup.cfg b/library/setup.cfg index a5fcae51..68d9d3f6 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- [metadata] name = enviroplus -version = 0.0.5 +version = 0.0.6 author = Philip Howard author_email = phil@pimoroni.com description = Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi From bd58ed8fde017088969593aa77a8d4788d6948f2 Mon Sep 17 00:00:00 2001 From: Andy Piper Date: Fri, 4 Mar 2022 23:55:35 +0000 Subject: [PATCH 31/69] trivial updates to correct for different script names minor README tweaks add link to homebridge plugin --- README.md | 14 +++++++------- install-bullseye.sh | 4 ++-- install.sh | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 76054ecb..2314c815 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus - [![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/enviroplus-python) [![Coverage Status](https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/enviroplus-python?branch=master) [![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) @@ -10,7 +9,7 @@ Designed for environmental monitoring, Enviro+ lets you measure air quality (pol # Installing -You're best using the "One-line" install method if you want all of the UART serial configuration for the PMS5003 particulate matter sensor to run automatically. +You are best using the "One-line" install method if you want all of the UART serial configuration for the PMS5003 particulate matter sensor to run automatically. **Note** The code in this repository supports both the Enviro+ and Enviro Mini boards. _The Enviro Mini board does not have the Gas sensor or the breakout for the PM sensor._ @@ -21,7 +20,7 @@ You're best using the "One-line" install method if you want all of the UART seri ## One-line (Installs from GitHub) -``` +```bash curl -sSL https://get.pimoroni.com/enviroplus | bash ``` @@ -33,13 +32,13 @@ curl -sSL https://get.pimoroni.com/enviroplus | bash * `cd enviroplus-python` * `sudo ./install.sh` -**Note** Raspbian Lite users may first need to install git: `sudo apt install git` +**Note** Raspbian/Raspberry Pi OS Lite users may first need to install git: `sudo apt install git` ## Or... Install from PyPi and configure manually: * Run `sudo python3 -m pip install enviroplus` -**Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: +**Note** this will not perform any of the required configuration changes on your Pi, you may additionally need to: * Enable i2c: `raspi-config nonint do_i2c 0` * Enable SPI: `raspi-config nonint do_spi 0` @@ -52,7 +51,7 @@ And if you're using a PMS5003 sensor you will need to: And install additional dependencies: -``` +```bash sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools ``` @@ -64,9 +63,10 @@ sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools * enviroplus_exporter - https://github.com/tijmenvandenbrink/enviroplus_exporter - Prometheus exporter (with added support for Luftdaten and InfluxDB Cloud) * homekit-enviroplus - https://github.com/sighmon/homekit-enviroplus - An Apple HomeKit accessory for the Pimoroni Enviro+ * go-enviroplus - https://github.com/rubiojr/go-enviroplus - Go modules to read Enviro+ sensors +* homebridge-enviroplus - https://github.com/mhawkshaw/homebridge-enviroplus - a Homebridge plugin to add the Enviro+ to HomeKit via Homebridge ## Help & Support * GPIO Pinout - https://pinout.xyz/pinout/enviro_plus -* Support forums - http://forums.pimoroni.com/c/support +* Support forums - https://forums.pimoroni.com/c/support * Discord - https://discord.gg/hr93ByC diff --git a/install-bullseye.sh b/install-bullseye.sh index 016c7ee9..9780175b 100755 --- a/install-bullseye.sh +++ b/install-bullseye.sh @@ -6,7 +6,7 @@ APT_HAS_UPDATED=false USER_HOME=/home/$SUDO_USER RESOURCES_TOP_DIR=$USER_HOME/Pimoroni WD=`pwd` -USAGE="sudo ./install.sh (--unstable)" +USAGE="sudo $0 (--unstable)" POSITIONAL_ARGS=() UNSTABLE=false PYTHON="/usr/bin/python3" @@ -21,7 +21,7 @@ distro_check() { user_check() { if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./install.sh'\n" + printf "Script must be run as root. Try 'sudo $0'\n" exit 1 fi } diff --git a/install.sh b/install.sh index 65f33422..66c9e4d7 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ APT_HAS_UPDATED=false USER_HOME=/home/$SUDO_USER RESOURCES_TOP_DIR=$USER_HOME/Pimoroni WD=`pwd` -USAGE="sudo ./install.sh (--unstable)" +USAGE="sudo $0 (--unstable)" POSITIONAL_ARGS=() UNSTABLE=false CODENAME=`lsb_release -sc` @@ -19,7 +19,7 @@ fi user_check() { if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./install.sh'\n" + printf "Script must be run as root. Try 'sudo $0'\n" exit 1 fi } From 611234cf8ec1abe7836aec0891085db75c9a79ac Mon Sep 17 00:00:00 2001 From: idotj Date: Fri, 3 Jun 2022 12:23:09 +0200 Subject: [PATCH 32/69] Add forked project Add a new web interface version, well documented and fully responsive. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2314c815..e0f23066 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools * homekit-enviroplus - https://github.com/sighmon/homekit-enviroplus - An Apple HomeKit accessory for the Pimoroni Enviro+ * go-enviroplus - https://github.com/rubiojr/go-enviroplus - Go modules to read Enviro+ sensors * homebridge-enviroplus - https://github.com/mhawkshaw/homebridge-enviroplus - a Homebridge plugin to add the Enviro+ to HomeKit via Homebridge +* Enviro Plus Web - https://gitlab.com/idotj/enviroplusweb - Simple Flask application serves a web page with the current sensor readings and a graph over a specified time period ## Help & Support From f1b282005af299c935f2c7dc7fcaf09b774ab839 Mon Sep 17 00:00:00 2001 From: Ed Rutherford <49820403+dedSyn4ps3@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:34:05 -0400 Subject: [PATCH 33/69] Contribution Updates (#117) Update contribution links Co-authored-by: EddieSneed --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2314c815..24e4a78b 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,10 @@ sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools ## Alternate Software & User Projects +* Enviro Plus Dashboard - https://gitlab.com/dedSyn4ps3/enviroplus-dashboard - A React-based web dashboard for viewing sensor data +* Enviro+ Example Projects - https://gitlab.com/dedSyn4ps3/enviroplus-python-projects - Includes original examples plus code to stream to Adafruit IO (more projects coming soon) * enviro monitor - https://github.com/roscoe81/enviro-monitor * mqtt-all - https://github.com/robmarkcole/rpi-enviro-mqtt - now upstream: [see examples/mqtt-all.py](examples/mqtt-all.py) -* adafruit_io.py - https://github.com/dedSyn4ps3/enviroplus-python/blob/master/examples/adafruit_io.py - uses Adafruit Blinka and BME280 libraries to publish to Adafruit IO * enviroplus_exporter - https://github.com/tijmenvandenbrink/enviroplus_exporter - Prometheus exporter (with added support for Luftdaten and InfluxDB Cloud) * homekit-enviroplus - https://github.com/sighmon/homekit-enviroplus - An Apple HomeKit accessory for the Pimoroni Enviro+ * go-enviroplus - https://github.com/rubiojr/go-enviroplus - Go modules to read Enviro+ sensors From 40553f165647d7bd73594ef92544de528e09a5eb Mon Sep 17 00:00:00 2001 From: Andy Piper Date: Thu, 18 Aug 2022 11:32:21 +0100 Subject: [PATCH 34/69] mqtt-all.py example: use retained publications Per [the change I suggested to the Pico-based Enviro code](https://github.com/pimoroni/enviro/pull/2), with this change, the MQTT messages will be published with the retain flag set, so that if a consumer is not subscribed, the most recent set of readings can still be read by a future subscriber later. This supports the [homebridge-enviroplus plugin](https://github.com/mhawkshaw/homebridge-enviroplus/issues/2#issuecomment-1215872947) better, so that a publication is more likely to exist on the topic even if the Pi with Enviroplus / this example code is not running at the time. Additional update to consider would be to align the message data formats between this sample and the newer Enviro product line. --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 8220c678..2338f0fe 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -262,7 +262,7 @@ def main(): values.update(pms_values) values["serial"] = device_serial_number print(values) - mqtt_client.publish(args.topic, json.dumps(values)) + mqtt_client.publish(args.topic, json.dumps(values), retain=True) display_status(disp, args.broker) time.sleep(args.interval) except Exception as e: From 81f6d6ea52b8a45dea23e3591c2e429d1f820012 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 25 Jul 2023 12:55:04 +0100 Subject: [PATCH 35/69] CI: Fix outdated Python versions and broken tests. --- .github/workflows/test.yml | 4 ++-- library/tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5fac95b2..e8e63580 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.9] + python: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v2 @@ -33,5 +33,5 @@ jobs: run: | python -m pip install coveralls coveralls --service=github - if: ${{ matrix.python == '3.9' }} + if: ${{ matrix.python == '3.11' }} diff --git a/library/tox.ini b/library/tox.ini index fcee0794..1b757864 100644 --- a/library/tox.ini +++ b/library/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist = py{36, 37, 38, 39},qa +envlist = py,qa skip_missing_interpreters = True [testenv] commands = python setup.py install - coverage run -m py.test -v -r wsx + coverage run -m pytest -v -r wsx coverage report deps = mock From 39d365f24b7b14e7828923be89b1d3d64bd3b645 Mon Sep 17 00:00:00 2001 From: Justin Pauley Date: Mon, 24 Jul 2023 19:03:54 -0400 Subject: [PATCH 36/69] Adjust Script To Prevent PMS Sensor Buffering --- examples/mqtt-all.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 2338f0fe..5e564afe 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -252,6 +252,9 @@ def main(): print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) print("MQTT broker IP: {}".format(args.broker)) + # Set an initial update time + update_time = time.time() + # Main loop to read data, display, and send over mqtt mqtt_client.loop_start() while True: @@ -260,11 +263,13 @@ def main(): if HAS_PMS: pms_values = read_pms5003(pms5003) values.update(pms_values) - values["serial"] = device_serial_number - print(values) - mqtt_client.publish(args.topic, json.dumps(values), retain=True) - display_status(disp, args.broker) - time.sleep(args.interval) + time_since_update = time.time() - update_time + if time_since_update >= args.interval: + update_time = time.time() + values["serial"] = device_serial_number + print(values) + mqtt_client.publish(args.topic, json.dumps(values), retain=True) + display_status(disp, args.broker) except Exception as e: print(e) From 550fac120543f945c38733a072c958a02705eea2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 23 Oct 2023 12:27:10 +0100 Subject: [PATCH 37/69] Repackage to latest boilerplate. --- .github/workflows/build.yml | 41 +++ .github/workflows/qa.yml | 36 +++ .github/workflows/test.yml | 22 +- library/CHANGELOG.txt => CHANGELOG.md | 0 library/MANIFEST.in => MANIFEST.in | 0 Makefile | 89 +++--- check-install.py | 109 -------- check.sh | 87 ++++++ .../enviroplus => enviroplus}/__init__.py | 0 {library/enviroplus => enviroplus}/gas.py | 0 {library/enviroplus => enviroplus}/noise.py | 0 install-bullseye.sh | 254 ------------------ install.sh | 183 +++++++++---- library/LICENSE.txt | 21 -- library/README.md | 107 -------- library/setup.cfg | 76 ------ library/setup.py | 33 --- library/tox.ini | 24 -- pyproject.toml | 145 ++++++++++ requirements-dev.txt | 9 + {library/tests => tests}/conftest.py | 0 {library/tests => tests}/test_noise.py | 0 {library/tests => tests}/test_setup.py | 0 tox.ini | 34 +++ uninstall.sh | 83 ++++-- 25 files changed, 590 insertions(+), 763 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/qa.yml rename library/CHANGELOG.txt => CHANGELOG.md (100%) rename library/MANIFEST.in => MANIFEST.in (100%) delete mode 100755 check-install.py create mode 100755 check.sh rename {library/enviroplus => enviroplus}/__init__.py (100%) rename {library/enviroplus => enviroplus}/gas.py (100%) rename {library/enviroplus => enviroplus}/noise.py (100%) delete mode 100755 install-bullseye.sh delete mode 100644 library/LICENSE.txt delete mode 100644 library/README.md delete mode 100644 library/setup.cfg delete mode 100755 library/setup.py delete mode 100644 library/tox.ini create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt rename {library/tests => tests}/conftest.py (100%) rename {library/tests => tests}/test_noise.py (100%) rename {library/tests => tests}/test_setup.py (100%) create mode 100644 tox.ini diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..87200efb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.9', '3.10', '3.11'] + + env: + RELEASE_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name || github.sha }}-py${{ matrix.python }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + + - name: Install Dependencies + run: | + make dev-deps + + - name: Build Packages + run: | + make build + + - name: Upload Packages + uses: actions/upload-artifact@v3 + with: + name: ${{ env.RELEASE_FILE }} + path: dist/ diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml new file mode 100644 index 00000000..4f858832 --- /dev/null +++ b/.github/workflows/qa.yml @@ -0,0 +1,36 @@ +name: QA + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + name: linting & spelling + runs-on: ubuntu-latest + + env: + TERM: xterm-256color + + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Set up Python '3,11' + uses: actions/setup-python@v3 + with: + python-version: '3.11' + + - name: Install Dependencies + run: | + make dev-deps + + - name: Run Quality Assurance + run: | + make qa + + - name: Run Code Checks + run: | + make check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8e63580..016a6780 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,37 +1,41 @@ -name: Python Tests +name: Tests on: pull_request: push: branches: - - master + - main jobs: test: + name: Python ${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: python: ['3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - name: Checkout Code + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} + - name: Install Dependencies run: | - python -m pip install --upgrade setuptools tox + make dev-deps + - name: Run Tests - working-directory: library run: | - tox -e py + make pytest + - name: Coverage + if: ${{ matrix.python == '3.9' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: library run: | python -m pip install coveralls coveralls --service=github - if: ${{ matrix.python == '3.11' }} diff --git a/library/CHANGELOG.txt b/CHANGELOG.md similarity index 100% rename from library/CHANGELOG.txt rename to CHANGELOG.md diff --git a/library/MANIFEST.in b/MANIFEST.in similarity index 100% rename from library/MANIFEST.in rename to MANIFEST.in diff --git a/Makefile b/Makefile index 12afc903..9e0c15c5 100644 --- a/Makefile +++ b/Makefile @@ -1,69 +1,60 @@ -LIBRARY_VERSION=$(shell grep version library/setup.cfg | awk -F" = " '{print $$2}') -LIBRARY_NAME=$(shell grep name library/setup.cfg | awk -F" = " '{print $$2}') +LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null) +LIBRARY_VERSION := $(shell hatch version 2> /dev/null) -.PHONY: usage install uninstall +.PHONY: usage install uninstall check pytest qa build-deps check tag wheel sdist clean dist testdeploy deploy usage: +ifdef LIBRARY_NAME @echo "Library: ${LIBRARY_NAME}" @echo "Version: ${LIBRARY_VERSION}\n" +else + @echo "WARNING: You should 'make dev-deps'\n" +endif @echo "Usage: make , where target is one of:\n" - @echo "install: install the library locally from source" - @echo "uninstall: uninstall the local library" - @echo "check: peform basic integrity checks on the codebase" - @echo "python-readme: generate library/README.rst from README.md" - @echo "python-wheels: build python .whl files for distribution" - @echo "python-sdist: build python source distribution" - @echo "python-clean: clean python build and dist directories" - @echo "python-dist: build all python distribution files" - @echo "python-testdeploy: build all and deploy to test PyPi" - @echo "tag: tag the repository with the current version" + @echo "install: install the library locally from source" + @echo "uninstall: uninstall the local library" + @echo "dev-deps: install Python dev dependencies" + @echo "check: perform basic integrity checks on the codebase" + @echo "qa: run linting and package QA" + @echo "pytest: run Python test fixtures" + @echo "clean: clean Python build and dist directories" + @echo "build: build Python distribution files" + @echo "testdeploy: build and upload to test PyPi" + @echo "deploy: build and upload to PyPi" + @echo "tag: tag the repository with the current version\n" install: - ./install.sh + ./install.sh --unstable uninstall: ./uninstall.sh -check: - @echo "Checking for trailing whitespace" - @! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO - @echo "Checking for DOS line-endings" - @! grep -IUrn --color " " --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile - @echo "Checking library/CHANGELOG.txt" - @cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION} - @echo "Checking library/${LIBRARY_NAME}/__init__.py" - @cat library/${LIBRARY_NAME}/__init__.py | grep "^__version__ = '${LIBRARY_VERSION}'" - -tag: - git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" +dev-deps: + python3 -m pip install -r requirements-dev.txt + sudo apt install dos2unix -python-readme: library/README.md - -python-license: library/LICENSE.txt +check: + @bash check.sh -library/README.md: README.md library/CHANGELOG.txt - cp README.md library/README.md - printf "\n# Changelog\n" >> library/README.md - cat library/CHANGELOG.txt >> library/README.md +qa: + tox -e qa -library/LICENSE.txt: LICENSE - cp LICENSE library/LICENSE.txt +pytest: + tox -e py -python-wheels: python-readme python-license - cd library; python3 setup.py bdist_wheel +nopost: + @bash check.sh --nopost -python-sdist: python-readme python-license - cd library; python3 setup.py sdist +tag: + git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" -python-clean: - -rm -r library/dist - -rm -r library/build - -rm -r library/*.egg-info +build: check + @hatch build -python-dist: python-clean python-wheels python-sdist - ls library/dist +clean: + -rm -r dist -python-testdeploy: python-dist - twine upload --repository-url https://test.pypi.org/legacy/ library/dist/* +testdeploy: build + twine upload --repository testpypi dist/* -python-deploy: check python-dist - twine upload library/dist/* +deploy: nopost build + twine upload dist/* diff --git a/check-install.py b/check-install.py deleted file mode 100755 index ea5eecd6..00000000 --- a/check-install.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -f"Sorry! This program requires Python >= 3.6 😅. Run with \"python3 check-install.py\"" - -CONFIG_FILE = "/boot/config.txt" - -print("""Checking Enviro+ install, please wait...""") - -errors = 0 -check_apt = False - -try: - import apt - check_apt = True -except ImportErorr: - print("⚠️ Could not import \"apt\". Unable to verify system dependencies.") - - -apt_deps = { - "python3", - "python3-pip", - "python3-numpy", - "python3-smbus", - "python3-pil", - "python3-cffi", - "python3-spidev", - "python3-rpi.gpio", - "libportaudio2" -} - -deps = { - "bme280": None, - "pms5003": None, - "ltr559": None, - "ST7735": None, - "ads1015": "0.0.7", - "fonts": None, - "font_roboto": None, - "astral": None, - "pytz": None, - "sounddevice": None, - "paho.mqtt": None -} - -config = { - "dtparam=i2c_arm=on", - "dtparam=spi=on", - "dtoverlay=adau7002-simple", - "dtoverlay=pi3-miniuart-bt", - "enable_uart=1" -} - -if check_apt: - print("\nSystem dependencies...") - print(" Retrieving cache...") - cache = apt.Cache() - - for dep in apt_deps: - installed = False - print(f" Checking for {dep}".ljust(35), end="") - try: - installed = cache[dep].is_installed - except KeyError: - pass - - if installed: - print("✅") - else: - print("⚠️ Missing!") - errors += 1 - -print("\nPython dependencies...") - -for dep, version in deps.items(): - print(f" Checking for {dep}".ljust(35), end="") - try: - __import__(dep) - print("✅") - except ImportError: - print("⚠️ Missing!") - errors += 1 - -print("\nSystem config...") - -config_txt = open(CONFIG_FILE, "r").read().split("\n") - -def check_config(line): - global errors - print(f" Checking for {line} in {CONFIG_FILE}: ", end="") - for cline in config_txt: - if cline.startswith(line): - print("✅") - return - print("⚠️ Missing!") - errors += 1 - -for line in config: - check_config(line) - -if errors > 0: - print("\n⚠️ Config errors were found! Something might be awry.") -else: - print("\n✅ Looks good from here!") - -print("\nHave you?") -print(" • Rebooted after installing") -print(" • Made sure to run examples with \"python3\"") -print(" • Checked for any errors when running \"sudo ./install.sh\"") diff --git a/check.sh b/check.sh new file mode 100755 index 00000000..cbb15653 --- /dev/null +++ b/check.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# This script handles some basic QA checks on the source + +NOPOST=$1 +LIBRARY_NAME=`hatch project metadata name` +LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'` +POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'` + +success() { + echo -e "$(tput setaf 2)$1$(tput sgr0)" +} + +inform() { + echo -e "$(tput setaf 6)$1$(tput sgr0)" +} + +warning() { + echo -e "$(tput setaf 1)$1$(tput sgr0)" +} + +while [[ $# -gt 0 ]]; do + K="$1" + case $K in + -p|--nopost) + NOPOST=true + shift + ;; + *) + if [[ $1 == -* ]]; then + printf "Unrecognised option: $1\n"; + exit 1 + fi + POSITIONAL_ARGS+=("$1") + shift + esac +done + +inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n" + +inform "Checking for trailing whitespace..." +grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO +if [[ $? -eq 0 ]]; then + warning "Trailing whitespace found!" + exit 1 +else + success "No trailing whitespace found." +fi +printf "\n" + +inform "Checking for DOS line-endings..." +grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile +if [[ $? -eq 0 ]]; then + warning "DOS line-endings found!" + exit 1 +else + success "No DOS line-endings found." +fi +printf "\n" + +inform "Checking CHANGELOG.md..." +cat CHANGELOG.md | grep ^${LIBRARY_VERSION} > /dev/null 2>&1 +if [[ $? -eq 1 ]]; then + warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md." + exit 1 +else + success "Changes found for version ${LIBRARY_VERSION}." +fi +printf "\n" + +inform "Checking for git tag ${LIBRARY_VERSION}..." +git tag -l | grep -E "${LIBRARY_VERSION}$" +if [[ $? -eq 1 ]]; then + warning "Missing git tag for version ${LIBRARY_VERSION}" +fi +printf "\n" + +if [[ $NOPOST ]]; then + inform "Checking for .postN on library version..." + if [[ "$POST_VERSION" != "" ]]; then + warning "Found .$POST_VERSION on library version." + inform "Please only use these for testpypi releases." + exit 1 + else + success "OK" + fi +fi diff --git a/library/enviroplus/__init__.py b/enviroplus/__init__.py similarity index 100% rename from library/enviroplus/__init__.py rename to enviroplus/__init__.py diff --git a/library/enviroplus/gas.py b/enviroplus/gas.py similarity index 100% rename from library/enviroplus/gas.py rename to enviroplus/gas.py diff --git a/library/enviroplus/noise.py b/enviroplus/noise.py similarity index 100% rename from library/enviroplus/noise.py rename to enviroplus/noise.py diff --git a/install-bullseye.sh b/install-bullseye.sh deleted file mode 100755 index 9780175b..00000000 --- a/install-bullseye.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/bin/bash -CONFIG=/boot/config.txt -DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` -CONFIG_BACKUP=false -APT_HAS_UPDATED=false -USER_HOME=/home/$SUDO_USER -RESOURCES_TOP_DIR=$USER_HOME/Pimoroni -WD=`pwd` -USAGE="sudo $0 (--unstable)" -POSITIONAL_ARGS=() -UNSTABLE=false -PYTHON="/usr/bin/python3" -CODENAME=`lsb_release -sc` - -distro_check() { - if [[ $CODENAME != "bullseye" ]]; then - printf "This installer is for Raspberry Pi OS: Bullseye only, current distro: $CODENAME\n" - exit 1 - fi -} - -user_check() { - if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo $0'\n" - exit 1 - fi -} - -confirm() { - if [ "$FORCE" == '-y' ]; then - true - else - read -r -p "$1 [y/N] " response < /dev/tty - if [[ $response =~ ^(yes|y|Y)$ ]]; then - true - else - false - fi - fi -} - -prompt() { - read -r -p "$1 [y/N] " response < /dev/tty - if [[ $response =~ ^(yes|y|Y)$ ]]; then - true - else - false - fi -} - -success() { - echo -e "$(tput setaf 2)$1$(tput sgr0)" -} - -inform() { - echo -e "$(tput setaf 6)$1$(tput sgr0)" -} - -warning() { - echo -e "$(tput setaf 1)$1$(tput sgr0)" -} - -function do_config_backup { - if [ ! $CONFIG_BACKUP == true ]; then - CONFIG_BACKUP=true - FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" - inform "Backing up $CONFIG to /boot/$FILENAME\n" - cp $CONFIG /boot/$FILENAME - mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME - if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER - fi - fi -} - -function apt_pkg_install { - PACKAGES=() - PACKAGES_IN=("$@") - for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do - PACKAGE="${PACKAGES_IN[$i]}" - if [ "$PACKAGE" == "" ]; then continue; fi - printf "Checking for $PACKAGE\n" - dpkg -L $PACKAGE > /dev/null 2>&1 - if [ "$?" == "1" ]; then - PACKAGES+=("$PACKAGE") - fi - done - PACKAGES="${PACKAGES[@]}" - if ! [ "$PACKAGES" == "" ]; then - echo "Installing missing packages: $PACKAGES" - if [ ! $APT_HAS_UPDATED ]; then - apt update - APT_HAS_UPDATED=true - fi - apt install -y $PACKAGES - if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" - fi - fi -} - -while [[ $# -gt 0 ]]; do - K="$1" - case $K in - -u|--unstable) - UNSTABLE=true - shift - ;; - -p|--python) - PYTHON=$2 - shift - shift - ;; - *) - if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; - printf "Usage: $USAGE\n"; - exit 1 - fi - POSITIONAL_ARGS+=("$1") - shift - esac -done - -distro_check -user_check - -if [ ! -f "$PYTHON" ]; then - printf "Python path $PYTHON not found!\n" - exit 1 -fi - -PYTHON_VER=`$PYTHON --version` - -inform "Installing. Please wait..." - -$PYTHON -m pip install --upgrade configparser - -CONFIG_VARS=`$PYTHON - < $UNINSTALLER -printf "It's recommended you run these steps manually.\n" -printf "If you want to run the full script, open it in\n" -printf "an editor and remove 'exit 1' from below.\n" -exit 1 -EOF - -printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" - -if $UNSTABLE; then - warning "Installing unstable library from source.\n\n" -else - printf "Installing stable library from pypi.\n\n" -fi - -cd library - -printf "Installing for $PYTHON_VER...\n" -apt_pkg_install "${PY3_DEPS[@]}" -if $UNSTABLE; then - $PYTHON setup.py install > /dev/null -else - $PYTHON -m pip install --upgrade $LIBRARY_NAME -fi -if [ $? -eq 0 ]; then - success "Done!\n" - echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER -fi - -cd $WD - -for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do - CMD="${SETUP_CMDS[$i]}" - # Attempt to catch anything that touches /boot/config.txt and trigger a backup - if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then - do_config_backup - fi - eval $CMD -done - -for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do - CONFIG_LINE="${CONFIG_TXT[$i]}" - if ! [ "$CONFIG_LINE" == "" ]; then - do_config_backup - inform "Adding $CONFIG_LINE to $CONFIG\n" - sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG - if ! grep -q "^$CONFIG_LINE" $CONFIG; then - printf "$CONFIG_LINE\n" >> $CONFIG - fi - fi -done - -if [ -d "examples" ]; then - if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then - inform "Copying examples to $RESOURCES_DIR" - cp -r examples/ $RESOURCES_DIR - echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER - success "Done!" - fi -fi - -printf "\n" - -if [ -f "/usr/bin/pydoc" ]; then - printf "Generating documentation.\n" - pydoc -w $LIBRARY_NAME > /dev/null - if [ -f "$LIBRARY_NAME.html" ]; then - cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html - rm -f $LIBRARY_NAME.html - inform "Documentation saved to $RESOURCES_DIR/docs.html" - success "Done!" - else - warning "Error: Failed to generate documentation." - fi -fi - -success "\nAll done!" -warning "If this is your first time installing you should --reboot-- for hardware changes to take effect.\n" -warning "This library is installed for python 3 *only* make sure to use \"python3\" when running examples.\n" diff --git a/install.sh b/install.sh index 66c9e4d7..bee710c7 100755 --- a/install.sh +++ b/install.sh @@ -1,31 +1,28 @@ #!/bin/bash - +LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` CONFIG=/boot/config.txt DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` CONFIG_BACKUP=false APT_HAS_UPDATED=false -USER_HOME=/home/$SUDO_USER -RESOURCES_TOP_DIR=$USER_HOME/Pimoroni +RESOURCES_TOP_DIR=$HOME/Pimoroni +VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh +VENV_DIR=$RESOURCES_TOP_DIR/venv WD=`pwd` -USAGE="sudo $0 (--unstable)" +USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() +FORCE=false UNSTABLE=false -CODENAME=`lsb_release -sc` - -if [[ $CODENAME == "bullseye" ]]; then - bash ./install-bullseye.sh $@ - exit $? -fi +PYTHON="python" user_check() { - if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo $0'\n" + if [ $(id -u) -eq 0 ]; then + printf "Script should not be run as root. Try './install.sh'\n" exit 1 fi } confirm() { - if [ "$FORCE" == '-y' ]; then + if $FORCE; then true else read -r -p "$1 [y/N] " response < /dev/tty @@ -58,12 +55,52 @@ warning() { echo -e "$(tput setaf 1)$1$(tput sgr0)" } +venv_bash_snippet() { + if [ ! -f $VENV_BASH_SNIPPET ]; then + cat << EOF > $VENV_BASH_SNIPPET +# Add \`source $RESOURCES_TOP_DIR/auto_venv.sh\` to your ~/.bashrc to activate +# the Pimoroni virtual environment automagically! +PY_ENV_DIR=~/Pimoroni/venv +if [ ! -f \$PY_ENV_DIR/bin/activate ]; then + printf "Creating user Python environment in \$PY_ENV_DIR, please wait...\n" + mkdir -p \$PY_ENV_DIR + python3 -m venv --system-site-packages --prompt Pimoroni \$PY_ENV_DIR +fi +printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n" +source \$PY_ENV_DIR/bin/activate +EOF + fi +} + +venv_check() { + PYTHON_BIN=`which $PYTHON` + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + if confirm "Would you like us to create one for you?"; then + if [ ! -f $VENV_DIR/bin/activate ]; then + inform "Creating virtual Python environment in $VENV_DIR, please wait...\n" + mkdir -p $VENV_DIR + /usr/bin/python3 -m venv $VENV_DIR --system-site-packages --prompt Pimoroni + venv_bash_snippet + else + inform "Found existing virtual Python environment in $VENV_DIR\n" + fi + inform "Activating virtual Python environment in $VENV_DIR..." + inform "source $VENV_DIR/bin/activate\n" + source $VENV_DIR/bin/activate + + else + exit 1 + fi + fi +} + function do_config_backup { if [ ! $CONFIG_BACKUP == true ]; then CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" inform "Backing up $CONFIG to /boot/$FILENAME\n" - cp $CONFIG /boot/$FILENAME + sudo cp $CONFIG /boot/$FILENAME mkdir -p $RESOURCES_TOP_DIR/config-backups/ cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME if [ -f "$UNINSTALLER" ]; then @@ -88,16 +125,20 @@ function apt_pkg_install { if ! [ "$PACKAGES" == "" ]; then echo "Installing missing packages: $PACKAGES" if [ ! $APT_HAS_UPDATED ]; then - apt update + sudo apt update APT_HAS_UPDATED=true fi - apt install -y $PACKAGES + sudo apt install -y $PACKAGES if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" + echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER fi fi } +function pip_pkg_install { + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@" +} + while [[ $# -gt 0 ]]; do K="$1" case $K in @@ -105,6 +146,15 @@ while [[ $# -gt 0 ]]; do UNSTABLE=true shift ;; + -f|--force) + FORCE=true + shift + ;; + -p|--python) + PYTHON=$2 + shift + shift + ;; *) if [[ $1 == -* ]]; then printf "Unrecognised option: $1\n"; @@ -117,28 +167,31 @@ while [[ $# -gt 0 ]]; do done user_check +venv_check -apt_pkg_install python-configparser - -CONFIG_VARS=`python - < $UNINSTALLER @@ -161,33 +221,25 @@ printf "It's recommended you run these steps manually.\n" printf "If you want to run the full script, open it in\n" printf "an editor and remove 'exit 1' from below.\n" exit 1 +source $VIRTUAL_ENV/bin/activate EOF -printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" - if $UNSTABLE; then warning "Installing unstable library from source.\n\n" else printf "Installing stable library from pypi.\n\n" fi -cd library - -if [ -f "/usr/bin/python3" ]; then - printf "Installing for Python 3..\n" - apt_pkg_install "${PY3_DEPS[@]}" - if $UNSTABLE; then - python3 setup.py install > /dev/null - else - pip3 install --upgrade $LIBRARY_NAME - fi - if [ $? -eq 0 ]; then - success "Done!\n" - echo "pip3 uninstall $LIBRARY_NAME" >> $UNINSTALLER - fi +inform "Installing for $PYTHON_VER...\n" +apt_pkg_install "${APT_PACKAGES[@]}" +if $UNSTABLE; then + pip_pkg_install . else - printf "/usr/bin/python3 not found. Unable to install!\n" - exit 1 + pip_pkg_install $LIBRARY_NAME +fi +if [ $? -eq 0 ]; then + success "Done!\n" + echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER fi cd $WD @@ -206,9 +258,9 @@ for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do if ! [ "$CONFIG_LINE" == "" ]; then do_config_backup inform "Adding $CONFIG_LINE to $CONFIG\n" - sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG + sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG if ! grep -q "^$CONFIG_LINE" $CONFIG; then - printf "$CONFIG_LINE\n" >> $CONFIG + printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG fi fi done @@ -222,7 +274,20 @@ if [ -d "examples" ]; then fi fi +printf "\n" + +if confirm "Would you like to generate documentation?"; then + pip_pkg_install pdoc + printf "Generating documentation.\n" + $PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null + if [ $? -eq 0 ]; then + inform "Documentation saved to $RESOURCES_DIR/docs" + success "Done!" + else + warning "Error: Failed to generate documentation." + fi +fi + success "\nAll done!" -warning "If this is your first time installing you should --reboot-- for hardware changes to take effect.\n" -warning "This library is installed for Python 3 *only* make sure to use \"python3\" when running examples.\n" +inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" inform "Find uninstall steps in $UNINSTALLER\n" diff --git a/library/LICENSE.txt b/library/LICENSE.txt deleted file mode 100644 index aed751a0..00000000 --- a/library/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Pimoroni Ltd. - -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. diff --git a/library/README.md b/library/README.md deleted file mode 100644 index 14988143..00000000 --- a/library/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# Enviro+ - -Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus - - -[![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/enviroplus-python) -[![Coverage Status](https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/enviroplus-python?branch=master) -[![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) -[![Python Versions](https://img.shields.io/pypi/pyversions/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) - -# Installing - -You're best using the "One-line" install method if you want all of the UART serial configuration for the PMS5003 particulate matter sensor to run automatically. - -**Note** The code in this repository supports both the Enviro+ and Enviro Mini boards. _The Enviro Mini board does not have the Gas sensor or the breakout for the PM sensor._ - -![Enviro Plus pHAT](./Enviro-Plus-pHAT.jpg) -![Enviro Mini pHAT](./Enviro-mini-pHAT.jpg) - -:warning: This library now supports Python 3 only, Python 2 is EOL - https://www.python.org/doc/sunset-python-2/ - -## One-line (Installs from GitHub) - -``` -curl -sSL https://get.pimoroni.com/enviroplus | bash -``` - -**Note** report issues with one-line installer here: https://github.com/pimoroni/get - -## Or... Install and configure dependencies from GitHub: - -* `git clone https://github.com/pimoroni/enviroplus-python` -* `cd enviroplus-python` -* `sudo ./install.sh` - -**Note** Raspbian Lite users may first need to install git: `sudo apt install git` - -## Or... Install from PyPi and configure manually: - -* Run `sudo python3 -m pip install enviroplus` - -**Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: - -* Enable i2c: `raspi-config nonint do_i2c 0` -* Enable SPI: `raspi-config nonint do_spi 0` - -And if you're using a PMS5003 sensor you will need to: - -* Enable serial: `raspi-config nonint set_config_var enable_uart 1 /boot/config.txt` -* Disable serial terminal: `sudo raspi-config nonint do_serial 1` -* Add `dtoverlay=pi3-miniuart-bt` to your `/boot/config.txt` - -And install additional dependencies: - -``` -sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools -``` - -## Alternate Software & User Projects - -* enviro monitor - https://github.com/roscoe81/enviro-monitor -* mqtt-all - https://github.com/robmarkcole/rpi-enviro-mqtt - now upstream: [see examples/mqtt-all.py](examples/mqtt-all.py) -* adafruit_io.py - https://github.com/dedSyn4ps3/enviroplus-python/blob/master/examples/adafruit_io.py - uses Adafruit Blinka and BME280 libraries to publish to Adafruit IO -* enviroplus_exporter - https://github.com/tijmenvandenbrink/enviroplus_exporter - Prometheus exporter (with added support for Luftdaten and InfluxDB Cloud) -* homekit-enviroplus - https://github.com/sighmon/homekit-enviroplus - An Apple HomeKit accessory for the Pimoroni Enviro+ -* go-enviroplus - https://github.com/rubiojr/go-enviroplus - Go modules to read Enviro+ sensors - -## Help & Support - -* GPIO Pinout - https://pinout.xyz/pinout/enviro_plus -* Support forums - http://forums.pimoroni.com/c/support -* Discord - https://discord.gg/hr93ByC - -# Changelog -0.0.6 ------ - -* Fix noise by specifying adau7002 device - -0.0.5 ------ - -* Drop Python 2.x support -* Add "available()" method for gas sensor - -0.0.4 ------ - -* Add support for ads1015 >= v0.0.7 (ADS1115 ADCs) -* Packaging tweaks - -0.0.3 ------ - -* Fix "self.noise_floor" bug in get_noise_profile - -0.0.2 ------ - -* Add support for extra ADC channel in Gas -* Handle breaking change in new ltr559 library -* Add Noise functionality - -0.0.1 ------ - -* Initial Release diff --git a/library/setup.cfg b/library/setup.cfg deleted file mode 100644 index 68d9d3f6..00000000 --- a/library/setup.cfg +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -[metadata] -name = enviroplus -version = 0.0.6 -author = Philip Howard -author_email = phil@pimoroni.com -description = Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi -long_description = file: README.md -long_description_content_type = text/markdown -keywords = Raspberry Pi -url = https://www.pimoroni.com -project_urls = - GitHub=https://www.github.com/pimoroni/enviroplus-python -license = MIT -# This includes the license file(s) in the wheel. -# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file -license_files = LICENSE.txt -classifiers = - Development Status :: 4 - Beta - Operating System :: POSIX :: Linux - License :: OSI Approved :: MIT License - Intended Audience :: Developers - Programming Language :: Python :: 3 - Topic :: Software Development - Topic :: Software Development :: Libraries - Topic :: System :: Hardware - -[options] -python_requires = >= 3.6 -packages = enviroplus -install_requires = - pimoroni-bme280 - pms5003 - ltr559 - st7735 - ads1015 >= 0.0.7 - fonts - font-roboto - astral - pytz - sounddevice - paho-mqtt - -[flake8] -exclude = - .tox, - .eggs, - .git, - __pycache__, - build, - dist -ignore = - E501 - -[pimoroni] -py2deps = -py3deps = - python3 - python3-pip - python3-numpy - python3-smbus - python3-pil - python3-cffi - python3-spidev - python3-rpi.gpio - libportaudio2 -configtxt = - dtoverlay=pi3-miniuart-bt - dtoverlay=adau7002-simple -commands = - printf "Setting up i2c and SPI..\n" - raspi-config nonint do_spi 0 - raspi-config nonint do_i2c 0 - printf "Setting up serial for PMS5003..\n" - raspi-config nonint do_serial 1 # Disable serial terminal over /dev/ttyAMA0 - raspi-config nonint set_config_var enable_uart 1 $CONFIG # Enable serial port diff --git a/library/setup.py b/library/setup.py deleted file mode 100755 index 40d6dbc3..00000000 --- a/library/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2016 Pimoroni - -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. -""" - -from setuptools import setup, __version__ -from pkg_resources import parse_version - -minimum_version = parse_version('30.4.0') - -if parse_version(__version__) < minimum_version: - raise RuntimeError("Package setuptools must be at least version {}".format(minimum_version)) - -setup() diff --git a/library/tox.ini b/library/tox.ini deleted file mode 100644 index 1b757864..00000000 --- a/library/tox.ini +++ /dev/null @@ -1,24 +0,0 @@ -[tox] -envlist = py,qa -skip_missing_interpreters = True - -[testenv] -commands = - python setup.py install - coverage run -m pytest -v -r wsx - coverage report -deps = - mock - pytest>=3.1 - pytest-cov - -[testenv:qa] -commands = - check-manifest --ignore tox.ini,tests*,.coveragerc - python setup.py check -m -r -s - flake8 --ignore E501 - rstcheck README.rst -deps = - check-manifest - flake8 - rstcheck diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0d296bf2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,145 @@ +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[project] +name = "enviroplus" +dynamic = ["version", "readme"] +description = "Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi" +license = {file = "LICENSE"} +requires-python = ">= 3.7" +authors = [ + { name = "Philip Howard", email = "phil@pimoroni.com" }, +] +maintainers = [ + { name = "Philip Howard", email = "phil@pimoroni.com" }, +] +keywords = [ + "Pi", + "Raspberry", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Hardware", +] +dependencies = [ + "pimoroni-bme280", + "pms5003", + "ltr559", + "st7735", + "ads1015 >= 0.0.7", + "fonts", + "font-roboto", + "astral", + "pytz", + "sounddevice", + "paho-mqtt" +] + +[project.urls] +GitHub = "https://www.github.com/pimoroni/enviroplus-python" +Homepage = "https://www.pimoroni.com" + +[tool.hatch.version] +path = "enviroplus/__init__.py" + +[tool.hatch.build] +include = [ + "enviroplus", + "README.md", + "CHANGELOG.md", + "LICENSE" +] + +[tool.hatch.build.targets.sdist] +include = [ + "*" +] +exclude = [ + ".*", + "dist" +] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" +fragments = [ + { path = "README.md" }, + { text = "\n" }, + { path = "CHANGELOG.md" } +] + +[tool.ruff] +exclude = [ + '.tox', + '.egg', + '.git', + '__pycache__', + 'build', + 'dist' +] +line-length = 200 + +[tool.codespell] +skip = """ +./.tox,\ +./.egg,\ +./.git,\ +./__pycache__,\ +./build,\ +./dist.\ +""" + +[tool.isort] +line_length = 200 + +[tool.check-manifest] +ignore = [ + '.stickler.yml', + 'boilerplate.md', + 'check.sh', + 'install.sh', + 'uninstall.sh', + 'Makefile', + 'tox.ini', + 'tests/*', + 'examples/*', + '.coveragerc', + 'requirements-dev.txt' +] + +[pimoroni] +apt_packages = [ + "python3", + "python3-pip", + "python3-numpy", + "python3-smbus", + "python3-pil", + "python3-cffi", + "python3-spidev", + "python3-rpi.gpio", + "libportaudio2" +] +configtxt = [ + "dtoverlay=pi3-miniuart-bt", + "dtoverlay=adau7002-simple" +] +commands = [ + "printf \"Setting up i2c and SPI..\n\"", + "raspi-config nonint do_spi 0", + "raspi-config nonint do_i2c 0", + "printf \"Setting up serial for PMS5003..\n\"", + "raspi-config nonint do_serial 1", + "raspi-config nonint set_config_var enable_uart 1 $CONFIG" +] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..525b0427 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +check-manifest +ruff +codespell +isort +twine +hatch +hatch-fancy-pypi-readme +tox +pdoc diff --git a/library/tests/conftest.py b/tests/conftest.py similarity index 100% rename from library/tests/conftest.py rename to tests/conftest.py diff --git a/library/tests/test_noise.py b/tests/test_noise.py similarity index 100% rename from library/tests/test_noise.py rename to tests/test_noise.py diff --git a/library/tests/test_setup.py b/tests/test_setup.py similarity index 100% rename from library/tests/test_setup.py rename to tests/test_setup.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..44c86546 --- /dev/null +++ b/tox.ini @@ -0,0 +1,34 @@ +[tox] +envlist = py,qa +skip_missing_interpreters = True +isolated_build = true +minversion = 4.0.0 + +[testenv] +commands = + coverage run -m pytest -v -r wsx + coverage report +deps = + mock + pytest>=3.1 + pytest-cov + build + +[testenv:qa] +commands = + check-manifest + python -m build --no-isolation + python -m twine check dist/* + isort --check . + ruff . + codespell . +deps = + check-manifest + ruff + codespell + isort + twine + build + hatch + hatch-fancy-pypi-readme + diff --git a/uninstall.sh b/uninstall.sh index e3174449..f213fc52 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,33 +1,72 @@ #!/bin/bash -LIBRARY_VERSION=`cat library/setup.cfg | grep version | awk -F" = " '{print $2}'` -LIBRARY_NAME=`cat library/setup.cfg | grep name | awk -F" = " '{print $2}'` +FORCE=false +LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME +PYTHON="python" -printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Uninstaller\n\n" -if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./uninstall.sh'\n" - exit 1 -fi +venv_check() { + PYTHON_BIN=`which $PYTHON` + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + exit 1 + fi +} -cd library +user_check() { + if [ $(id -u) -eq 0 ]; then + printf "Script should not be run as root. Try './uninstall.sh'\n" + exit 1 + fi +} -printf "Unnstalling for Python 2..\n" -pip uninstall $LIBRARY_NAME +confirm() { + if $FORCE; then + true + else + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi + fi +} -if [ -f "/usr/bin/pip3" ]; then - printf "Uninstalling for Python 3..\n" - pip3 uninstall $LIBRARY_NAME -fi +prompt() { + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi +} + +success() { + echo -e "$(tput setaf 2)$1$(tput sgr0)" +} + +inform() { + echo -e "$(tput setaf 6)$1$(tput sgr0)" +} -cd .. +warning() { + echo -e "$(tput setaf 1)$1$(tput sgr0)" +} -printf "Disabling serial..\n" -# Enable serial terminal over /dev/ttyAMA0 -raspi-config nonint do_serial 0 -# Disable serial port -raspi-config nonint set_config_var enable_uart 0 /boot/config.txt -# Switch serial port back to miniUART -sed -i 's/^dtoverlay=pi3-miniuart-bt # for Enviro+/#dtoverlay=pi3-miniuart-bt # for Enviro+/' /boot/config.txt +printf "$LIBRARY_NAME Python Library: Uninstaller\n\n" + +user_check +venv_check + +printf "Uninstalling for Python 3...\n" +$PYTHON -m pip uninstall $LIBRARY_NAME + +if [ -d $RESOURCES_DIR ]; then + if confirm "Would you like to delete $RESOURCES_DIR?"; then + rm -r $RESOURCES_DIR + fi +fi printf "Done!\n" From 1f2df5a68d914c3e3e7f33aa7f911808f52ca527 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 23 Oct 2023 12:29:52 +0100 Subject: [PATCH 38/69] QA: Fix isort suggestions. --- enviroplus/gas.py | 3 ++- enviroplus/noise.py | 2 +- examples/adc.py | 3 ++- examples/all-in-one-enviro-mini.py | 16 +++++++++------- examples/all-in-one-no-pm.py | 14 ++++++++------ examples/all-in-one.py | 17 ++++++++++------- examples/combined.py | 20 ++++++++++++-------- examples/compensated-temperature.py | 1 + examples/gas.py | 3 ++- examples/lcd.py | 5 +++-- examples/light.py | 3 ++- examples/luftdaten.py | 10 ++++++---- examples/luftdaten_combined.py | 12 +++++++----- examples/mqtt-all.py | 12 +++++++----- examples/noise-amps-at-freqs.py | 1 + examples/noise-profile.py | 1 + examples/particulates.py | 3 ++- examples/weather-and-light.py | 17 ++++++++--------- examples/weather.py | 1 + tests/conftest.py | 1 + 20 files changed, 86 insertions(+), 59 deletions(-) diff --git a/enviroplus/gas.py b/enviroplus/gas.py index 54c240f6..6e96d8e8 100644 --- a/enviroplus/gas.py +++ b/enviroplus/gas.py @@ -1,7 +1,8 @@ """Read the MICS6814 via an ads1015 ADC""" -import time import atexit +import time + import ads1015 import RPi.GPIO as GPIO diff --git a/enviroplus/noise.py b/enviroplus/noise.py index 0d413bde..873fc26b 100644 --- a/enviroplus/noise.py +++ b/enviroplus/noise.py @@ -1,5 +1,5 @@ -import sounddevice import numpy +import sounddevice class Noise(): diff --git a/examples/adc.py b/examples/adc.py index a345d232..6a86242c 100755 --- a/examples/adc.py +++ b/examples/adc.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 +import logging import time + from enviroplus import gas -import logging logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/all-in-one-enviro-mini.py b/examples/all-in-one-enviro-mini.py index d7a001ff..9e87784c 100755 --- a/examples/all-in-one-enviro-mini.py +++ b/examples/all-in-one-enviro-mini.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -import time import colorsys import os import sys +import time + import ST7735 + try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 @@ -12,14 +14,14 @@ except ImportError: import ltr559 -from bme280 import BME280 -from enviroplus import gas +import logging from subprocess import PIPE, Popen -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont + +from bme280 import BME280 from fonts.ttf import RobotoMedium as UserFont -import logging +from PIL import Image, ImageDraw, ImageFont + +from enviroplus import gas logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/all-in-one-no-pm.py b/examples/all-in-one-no-pm.py index ec1ed1f3..aa67fef7 100755 --- a/examples/all-in-one-no-pm.py +++ b/examples/all-in-one-no-pm.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -import time import colorsys import os import sys +import time + import ST7735 + try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 @@ -12,13 +14,13 @@ except ImportError: import ltr559 +import logging + from bme280 import BME280 -from enviroplus import gas -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont from fonts.ttf import RobotoMedium as UserFont -import logging +from PIL import Image, ImageDraw, ImageFont + +from enviroplus import gas logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/all-in-one.py b/examples/all-in-one.py index ac539998..54986036 100755 --- a/examples/all-in-one.py +++ b/examples/all-in-one.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -import time import colorsys import sys +import time + import ST7735 + try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 @@ -11,14 +13,15 @@ except ImportError: import ltr559 +import logging + from bme280 import BME280 -from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError -from enviroplus import gas -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont from fonts.ttf import RobotoMedium as UserFont -import logging +from PIL import Image, ImageDraw, ImageFont +from pms5003 import PMS5003 +from pms5003 import ReadTimeoutError as pmsReadTimeoutError + +from enviroplus import gas logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/combined.py b/examples/combined.py index 556bcb0c..1ffaccb6 100755 --- a/examples/combined.py +++ b/examples/combined.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -import time import colorsys import sys +import time + import ST7735 + try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 @@ -11,15 +13,17 @@ except ImportError: import ltr559 -from bme280 import BME280 -from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError, SerialTimeoutError -from enviroplus import gas +import logging from subprocess import PIPE, Popen -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont + +from bme280 import BME280 from fonts.ttf import RobotoMedium as UserFont -import logging +from PIL import Image, ImageDraw, ImageFont +from pms5003 import PMS5003 +from pms5003 import ReadTimeoutError as pmsReadTimeoutError +from pms5003 import SerialTimeoutError + +from enviroplus import gas logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/compensated-temperature.py b/examples/compensated-temperature.py index b648f576..1d36140c 100755 --- a/examples/compensated-temperature.py +++ b/examples/compensated-temperature.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import time + from bme280 import BME280 try: diff --git a/examples/gas.py b/examples/gas.py index 5d72cb93..a107696a 100755 --- a/examples/gas.py +++ b/examples/gas.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 +import logging import time + from enviroplus import gas -import logging logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/lcd.py b/examples/lcd.py index 10413b91..0c0606e0 100755 --- a/examples/lcd.py +++ b/examples/lcd.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 +import logging + import ST7735 -from PIL import Image, ImageDraw, ImageFont from fonts.ttf import RobotoMedium as UserFont -import logging +from PIL import Image, ImageDraw, ImageFont logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/light.py b/examples/light.py index db61e6a2..ead18a20 100755 --- a/examples/light.py +++ b/examples/light.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 -import time import logging +import time + try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 diff --git a/examples/luftdaten.py b/examples/luftdaten.py index a78909fe..9e58dba8 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -1,18 +1,20 @@ #!/usr/bin/env python3 +import time +from subprocess import PIPE, Popen, check_output + import requests import ST7735 -import time from bme280 import BME280 -from pms5003 import PMS5003, ReadTimeoutError, ChecksumMismatchError -from subprocess import PIPE, Popen, check_output -from PIL import Image, ImageDraw, ImageFont from fonts.ttf import RobotoMedium as UserFont +from PIL import Image, ImageDraw, ImageFont +from pms5003 import PMS5003, ChecksumMismatchError, ReadTimeoutError try: from smbus2 import SMBus except ImportError: from smbus import SMBus + import logging logging.basicConfig( diff --git a/examples/luftdaten_combined.py b/examples/luftdaten_combined.py index 1d920db4..19e9fdc9 100644 --- a/examples/luftdaten_combined.py +++ b/examples/luftdaten_combined.py @@ -1,14 +1,16 @@ +import colorsys import logging import sys +import time +from subprocess import PIPE, Popen, check_output + import requests import ST7735 -import time -import colorsys from bme280 import BME280 -from pms5003 import PMS5003, ReadTimeoutError -from subprocess import PIPE, Popen, check_output -from PIL import Image, ImageDraw, ImageFont from fonts.ttf import RobotoMedium as UserFont +from PIL import Image, ImageDraw, ImageFont +from pms5003 import PMS5003, ReadTimeoutError + from enviroplus import gas try: diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 5e564afe..efda0b7b 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -6,11 +6,13 @@ """ import argparse -import ST7735 -import time import ssl +import time + +import ST7735 from bme280 import BME280 from pms5003 import PMS5003, ReadTimeoutError, SerialTimeoutError + from enviroplus import gas try: @@ -21,13 +23,13 @@ except ImportError: import ltr559 -from subprocess import PIPE, Popen, check_output -from PIL import Image, ImageDraw, ImageFont -from fonts.ttf import RobotoMedium as UserFont import json +from subprocess import PIPE, Popen, check_output import paho.mqtt.client as mqtt import paho.mqtt.publish as publish +from fonts.ttf import RobotoMedium as UserFont +from PIL import Image, ImageDraw, ImageFont try: from smbus2 import SMBus diff --git a/examples/noise-amps-at-freqs.py b/examples/noise-amps-at-freqs.py index 4c14c585..ddf1a315 100755 --- a/examples/noise-amps-at-freqs.py +++ b/examples/noise-amps-at-freqs.py @@ -1,5 +1,6 @@ import ST7735 from PIL import Image, ImageDraw + from enviroplus.noise import Noise print("""noise-amps-at-freqs.py - Measure amplitude from specific frequency bins diff --git a/examples/noise-profile.py b/examples/noise-profile.py index 40844391..c37ba35c 100755 --- a/examples/noise-profile.py +++ b/examples/noise-profile.py @@ -1,5 +1,6 @@ import ST7735 from PIL import Image, ImageDraw + from enviroplus.noise import Noise print("""noise-profile.py - Get a simple noise profile. diff --git a/examples/particulates.py b/examples/particulates.py index 04a49500..88d30e0c 100755 --- a/examples/particulates.py +++ b/examples/particulates.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 +import logging import time + from pms5003 import PMS5003, ReadTimeoutError -import logging logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', diff --git a/examples/weather-and-light.py b/examples/weather-and-light.py index cd8ae962..926c07b8 100755 --- a/examples/weather-and-light.py +++ b/examples/weather-and-light.py @@ -3,22 +3,21 @@ f"Sorry! This program requires Python >= 3.6 😅" +import colorsys import os import time -import numpy -import colorsys -from PIL import Image, ImageDraw, ImageFont, ImageFilter -from fonts.ttf import RobotoMedium as UserFont +from datetime import datetime, timedelta +import numpy +import pytz import ST7735 +from astral.geocoder import database, lookup +from astral.sun import sun from bme280 import BME280 +from fonts.ttf import RobotoMedium as UserFont from ltr559 import LTR559 - -import pytz +from PIL import Image, ImageDraw, ImageFilter, ImageFont from pytz import timezone -from astral.geocoder import database, lookup -from astral.sun import sun -from datetime import datetime, timedelta try: from smbus2 import SMBus diff --git a/examples/weather.py b/examples/weather.py index 66f18e0a..23a6a86e 100755 --- a/examples/weather.py +++ b/examples/weather.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import time + from bme280 import BME280 try: diff --git a/tests/conftest.py b/tests/conftest.py index b3fa3766..a7b3b333 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ that might otherwise have runtime side-effects. """ import sys + import mock import pytest from i2cdevice import MockSMBus From 207d6eb18cf79343bce05a5b0f6e54be96d9e173 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 23 Oct 2023 12:35:41 +0100 Subject: [PATCH 39/69] QA: Apply ruff suggestions. --- examples/all-in-one-enviro-mini.py | 2 -- examples/luftdaten.py | 2 +- examples/luftdaten_combined.py | 1 - examples/mqtt-all.py | 1 - examples/weather-and-light.py | 3 --- tests/test_setup.py | 10 +++++----- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/examples/all-in-one-enviro-mini.py b/examples/all-in-one-enviro-mini.py index 9e87784c..449c49d8 100755 --- a/examples/all-in-one-enviro-mini.py +++ b/examples/all-in-one-enviro-mini.py @@ -21,8 +21,6 @@ from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont -from enviroplus import gas - logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', level=logging.INFO, diff --git a/examples/luftdaten.py b/examples/luftdaten.py index 9e58dba8..f71ee43c 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import time -from subprocess import PIPE, Popen, check_output +from subprocess import check_output import requests import ST7735 diff --git a/examples/luftdaten_combined.py b/examples/luftdaten_combined.py index 19e9fdc9..978ce283 100644 --- a/examples/luftdaten_combined.py +++ b/examples/luftdaten_combined.py @@ -1,6 +1,5 @@ import colorsys import logging -import sys import time from subprocess import PIPE, Popen, check_output diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index efda0b7b..16e8f0df 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -27,7 +27,6 @@ from subprocess import PIPE, Popen, check_output import paho.mqtt.client as mqtt -import paho.mqtt.publish as publish from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont diff --git a/examples/weather-and-light.py b/examples/weather-and-light.py index 926c07b8..12b43def 100755 --- a/examples/weather-and-light.py +++ b/examples/weather-and-light.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -f"Sorry! This program requires Python >= 3.6 😅" - import colorsys import os import time @@ -17,7 +15,6 @@ from fonts.ttf import RobotoMedium as UserFont from ltr559 import LTR559 from PIL import Image, ImageDraw, ImageFilter, ImageFont -from pytz import timezone try: from smbus2 import SMBus diff --git a/tests/test_setup.py b/tests/test_setup.py index 40bf80d6..9b300e1f 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -12,7 +12,7 @@ def test_gas_unavailable(GPIO, mocksmbus): from enviroplus import gas mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh noes!") gas._is_setup = False - assert gas.available() == False + assert gas.available() is False with pytest.raises(RuntimeError): gas.read_all() @@ -21,7 +21,7 @@ def test_gas_unavailable(GPIO, mocksmbus): def test_gas_available(GPIO, smbus_notimeout): from enviroplus import gas gas._is_setup = False - assert gas.available() == True + assert gas.available() is True def test_gas_read_all(GPIO, smbus): @@ -29,13 +29,13 @@ def test_gas_read_all(GPIO, smbus): gas._is_setup = False result = gas.read_all() - assert type(result.oxidising) == float + assert isinstance(result.oxidising, float) assert int(result.oxidising) == 16641 - assert type(result.reducing) == float + assert isinstance(result.reducing, float) assert int(result.reducing) == 16727 - assert type(result.nh3) == float + assert isinstance(result.nh3, float) assert int(result.nh3) == 16813 assert "Oxidising" in str(result) From fc5a49bbacae740aa431aa9bc6fd9215616f6f54 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 23 Oct 2023 12:38:44 +0100 Subject: [PATCH 40/69] QA: Apply spelling corrections. --- enviroplus/gas.py | 2 +- enviroplus/noise.py | 4 ++-- examples/luftdaten.py | 2 +- tests/test_setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enviroplus/gas.py b/enviroplus/gas.py index 6e96d8e8..fe60fb89 100644 --- a/enviroplus/gas.py +++ b/enviroplus/gas.py @@ -92,7 +92,7 @@ def cleanup(): def read_all(): - """Return gas resistence for oxidising, reducing and NH3""" + """Return gas resistance for oxidising, reducing and NH3""" setup() if not _is_available: diff --git a/enviroplus/noise.py b/enviroplus/noise.py index 873fc26b..e9288b2e 100644 --- a/enviroplus/noise.py +++ b/enviroplus/noise.py @@ -39,7 +39,7 @@ def get_amplitude_at_frequency_range(self, start, end): """ n = self.sample_rate // 2 if start > n or end > n: - raise ValueError("Maxmimum frequency is {}".format(n)) + raise ValueError("Maximum frequency is {}".format(n)) recording = self._record() magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate)) @@ -50,7 +50,7 @@ def get_noise_profile(self, low=0.12, mid=0.36, high=None): - """Returns a noise charateristic profile. + """Returns a noise characteristic profile. Bins all frequencies into 3 weighted groups expressed as a percentage of the total frequency range. diff --git a/examples/luftdaten.py b/examples/luftdaten.py index f71ee43c..969c9f16 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -72,7 +72,7 @@ def read_values(): values["P2"] = str(pm_values.pm_ug_per_m3(2.5)) values["P1"] = str(pm_values.pm_ug_per_m3(10)) except(ReadTimeoutError, ChecksumMismatchError): - logging.info("Failed to read PMS5003. Reseting and retrying.") + logging.info("Failed to read PMS5003. Resetting and retrying.") pms5003.reset() pm_values = pms5003.read() values["P2"] = str(pm_values.pm_ug_per_m3(2.5)) diff --git a/tests/test_setup.py b/tests/test_setup.py index 9b300e1f..ac254e02 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -10,7 +10,7 @@ def test_gas_setup(GPIO, smbus): def test_gas_unavailable(GPIO, mocksmbus): from enviroplus import gas - mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh noes!") + mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh no!") gas._is_setup = False assert gas.available() is False From 6e9e4318af70449a81b5537cd0cb4182aea03a17 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 23 Oct 2023 12:49:48 +0100 Subject: [PATCH 41/69] Install: Fix for do_serial showing a UI. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0d296bf2..a39b5514 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,6 +140,6 @@ commands = [ "raspi-config nonint do_spi 0", "raspi-config nonint do_i2c 0", "printf \"Setting up serial for PMS5003..\n\"", - "raspi-config nonint do_serial 1", - "raspi-config nonint set_config_var enable_uart 1 $CONFIG" + "sudo raspi-config nonint do_serial_cons 1", + "sudo raspi-config nonint do_serial_hw 1" ] From 342cf4ee4be92c805c0d44ee8a1039dd6e0e0aa2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 15 Nov 2023 11:24:23 +0000 Subject: [PATCH 42/69] Bump requirement versions. --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a39b5514..bf030cc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,11 +35,11 @@ classifiers = [ "Topic :: System :: Hardware", ] dependencies = [ - "pimoroni-bme280", - "pms5003", - "ltr559", - "st7735", - "ads1015 >= 0.0.7", + "pimoroni-bme280 >= 1.0.0", + "pms5003 >= 1.0.0", + "ltr559 >= 1.0.0", + "st7735 >= 1.0.0", + "ads1015 >= 1.0.0", "fonts", "font-roboto", "astral", From 2cfed377ddcf5af73dae26dd01c60a879343e0fa Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 15 Nov 2023 13:01:04 +0000 Subject: [PATCH 43/69] Port to new libraries. Fixup examples and tests. --- enviroplus/__init__.py | 2 +- enviroplus/gas.py | 58 +++++++++------- enviroplus/noise.py | 18 ++--- examples/adc.py | 4 +- examples/all-in-one-enviro-mini.py | 24 +++---- examples/all-in-one-no-pm.py | 18 ++--- examples/all-in-one.py | 18 ++--- examples/combined.py | 26 +++---- examples/compensated-temperature.py | 15 ++-- examples/gas.py | 4 +- examples/lcd.py | 19 ++--- examples/light.py | 10 +-- examples/luftdaten.py | 80 +++++++++++---------- examples/luftdaten_combined.py | 49 +++++++------ examples/mqtt-all.py | 28 +++++--- examples/noise-amps-at-freqs.py | 16 +++-- examples/noise-profile.py | 16 +++-- examples/particulates.py | 4 +- examples/weather-and-light.py | 50 +++++++------ examples/weather.py | 21 +++--- pyproject.toml | 3 + tests/conftest.py | 104 ++++++++++++++-------------- tests/test_noise.py | 4 +- tests/test_setup.py | 23 +++--- 24 files changed, 312 insertions(+), 302 deletions(-) diff --git a/enviroplus/__init__.py b/enviroplus/__init__.py index fa9c4ec2..034f46c3 100644 --- a/enviroplus/__init__.py +++ b/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = '0.0.6' +__version__ = "0.0.6" diff --git a/enviroplus/gas.py b/enviroplus/gas.py index fe60fb89..e1c4b76d 100644 --- a/enviroplus/gas.py +++ b/enviroplus/gas.py @@ -4,20 +4,29 @@ import time import ads1015 -import RPi.GPIO as GPIO +import gpiod +import gpiodevice +from gpiod.line import Direction, Value -MICS6814_HEATER_PIN = 24 MICS6814_GAIN = 6.144 +OUTH = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.ACTIVE) +PLATFORMS = { + "Radxa ROCK 5B": {"heater": ("PIN_18", OUTH)}, + "Raspberry Pi 5": {"heater": ("PIN18", OUTH)}, + "Raspberry Pi 4": {"heater": ("GPIO24", OUTH)} +} + ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE _is_setup = False _is_available = False _adc_enabled = False _adc_gain = 6.148 +_heater = None class Mics6814Reading(object): - __slots__ = 'oxidising', 'reducing', 'nh3', 'adc' + __slots__ = "oxidising", "reducing", "nh3", "adc" def __init__(self, ox, red, nh3, adc=None): self.oxidising = ox @@ -26,24 +35,20 @@ def __init__(self, ox, red, nh3, adc=None): self.adc = adc def __repr__(self): - fmt = """Oxidising: {ox:05.02f} Ohms -Reducing: {red:05.02f} Ohms -NH3: {nh3:05.02f} Ohms""" + fmt = f"""Oxidising: {self.oxidising:05.02f} Ohms +Reducing: {self.reducing:05.02f} Ohms +NH3: {self.nh3:05.02f} Ohms""" if self.adc is not None: - fmt += """ -ADC: {adc:05.02f} Volts + fmt += f""" +ADC: {self.adc:05.02f} Volts """ - return fmt.format( - ox=self.oxidising, - red=self.reducing, - nh3=self.nh3, - adc=self.adc) + return fmt __str__ = __repr__ def setup(): - global adc, adc_type, _is_setup, _is_available + global adc, adc_type, _is_setup, _is_available, _heater if _is_setup: return _is_setup = True @@ -56,17 +61,15 @@ def setup(): _is_available = False return - adc.set_mode('single') + adc.set_mode("single") adc.set_programmable_gain(MICS6814_GAIN) - if adc_type == 'ADS1115': + if adc_type == "ADS1115": adc.set_sample_rate(128) else: adc.set_sample_rate(1600) - GPIO.setwarnings(False) - GPIO.setmode(GPIO.BCM) - GPIO.setup(MICS6814_HEATER_PIN, GPIO.OUT) - GPIO.output(MICS6814_HEATER_PIN, 1) + _heater = gpiodevice.get_pins_for_platform(PLATFORMS)[0] + atexit.register(cleanup) @@ -88,7 +91,10 @@ def set_adc_gain(value): def cleanup(): - GPIO.output(MICS6814_HEATER_PIN, 0) + if _heater is None: + return + lines, offset = _heater + lines.set_value(offset, Value.INACTIVE) def read_all(): @@ -98,9 +104,9 @@ def read_all(): if not _is_available: raise RuntimeError("Gas sensor not connected.") - ox = adc.get_voltage('in0/gnd') - red = adc.get_voltage('in1/gnd') - nh3 = adc.get_voltage('in2/gnd') + ox = adc.get_voltage("in0/gnd") + red = adc.get_voltage("in1/gnd") + nh3 = adc.get_voltage("in2/gnd") try: ox = (ox * 56000) / (3.3 - ox) @@ -121,11 +127,11 @@ def read_all(): if _adc_enabled: if _adc_gain == MICS6814_GAIN: - analog = adc.get_voltage('ref/gnd') + analog = adc.get_voltage("ref/gnd") else: adc.set_programmable_gain(_adc_gain) time.sleep(0.05) - analog = adc.get_voltage('ref/gnd') + analog = adc.get_voltage("ref/gnd") adc.set_programmable_gain(MICS6814_GAIN) return Mics6814Reading(ox, red, nh3, analog) diff --git a/enviroplus/noise.py b/enviroplus/noise.py index e9288b2e..261c3abf 100644 --- a/enviroplus/noise.py +++ b/enviroplus/noise.py @@ -2,10 +2,8 @@ import sounddevice -class Noise(): - def __init__(self, - sample_rate=16000, - duration=0.5): +class Noise: + def __init__(self, sample_rate=16000, duration=0.5): """Noise measurement. :param sample_rate: Sample rate in Hz @@ -39,17 +37,13 @@ def get_amplitude_at_frequency_range(self, start, end): """ n = self.sample_rate // 2 if start > n or end > n: - raise ValueError("Maximum frequency is {}".format(n)) + raise ValueError(f"Maximum frequency is {n}") recording = self._record() magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate)) return numpy.mean(magnitude[start:end]) - def get_noise_profile(self, - noise_floor=100, - low=0.12, - mid=0.36, - high=None): + def get_noise_profile(self, noise_floor=100, low=0.12, mid=0.36, high=None): """Returns a noise characteristic profile. Bins all frequencies into 3 weighted groups expressed as a percentage of the total frequency range. @@ -83,9 +77,9 @@ def get_noise_profile(self, def _record(self): return sounddevice.rec( int(self.duration * self.sample_rate), - device='adau7002', + device="adau7002", samplerate=self.sample_rate, blocking=True, channels=1, - dtype='float64' + dtype="float64" ) diff --git a/examples/adc.py b/examples/adc.py index 6a86242c..983aec3a 100755 --- a/examples/adc.py +++ b/examples/adc.py @@ -6,9 +6,9 @@ from enviroplus import gas logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""adc.py - Print readings from the MICS6814 Gas sensor. diff --git a/examples/all-in-one-enviro-mini.py b/examples/all-in-one-enviro-mini.py index 449c49d8..01491f37 100755 --- a/examples/all-in-one-enviro-mini.py +++ b/examples/all-in-one-enviro-mini.py @@ -5,7 +5,7 @@ import sys import time -import ST7735 +import st7735 try: # Transitional fix for breaking change in LTR559 @@ -22,11 +22,11 @@ from PIL import Image, ImageDraw, ImageFont logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") -logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors +logging.info("""all-in-one.py - Displays readings from all of Enviro plus" sensors Press Ctrl+C to exit! """) @@ -34,11 +34,11 @@ bme280 = BME280() # Create ST7735 LCD display class -st7735 = ST7735.ST7735( +st7735 = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -50,7 +50,7 @@ HEIGHT = st7735.height # Set up canvas and font -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) path = os.path.dirname(os.path.realpath(__file__)) font_size = 20 @@ -71,7 +71,7 @@ def display_text(variable, data, unit): vmax = max(values[variable]) colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] # Format the variable name and value - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) for i in range(len(colours)): @@ -90,9 +90,9 @@ def display_text(variable, data, unit): # Get the temperature of the CPU for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) + process = Popen(["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True) output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + return float(output[output.index("=") + 1:output.rindex("'")]) # Tuning factor for compensation. Decrease this number to adjust the @@ -131,7 +131,7 @@ def get_cpu_temperature(): # One mode for each variable if mode == 0: # variable = "temperature" - unit = "C" + unit = "°C" cpu_temp = get_cpu_temperature() # Smooth out with some averaging to decrease jitter cpu_temps = cpu_temps[1:] + [cpu_temp] diff --git a/examples/all-in-one-no-pm.py b/examples/all-in-one-no-pm.py index aa67fef7..c2d9078d 100755 --- a/examples/all-in-one-no-pm.py +++ b/examples/all-in-one-no-pm.py @@ -5,7 +5,7 @@ import sys import time -import ST7735 +import st7735 try: # Transitional fix for breaking change in LTR559 @@ -23,9 +23,9 @@ from enviroplus import gas logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors Press Ctrl+C to exit! @@ -35,11 +35,11 @@ bme280 = BME280() # Create ST7735 LCD display class -st7735 = ST7735.ST7735( +st7735 = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -51,7 +51,7 @@ HEIGHT = st7735.height # Set up canvas and font -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) path = os.path.dirname(os.path.realpath(__file__)) font_size = 20 @@ -72,7 +72,7 @@ def display_text(variable, data, unit): vmax = max(values[variable]) colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] # Format the variable name and value - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) for i in range(len(colours)): @@ -136,7 +136,7 @@ def get_cpu_temperature(): # One mode for each variable if mode == 0: # variable = "temperature" - unit = "C" + unit = "°C" cpu_temp = get_cpu_temperature() # Smooth out with some averaging to decrease jitter cpu_temps = cpu_temps[1:] + [cpu_temp] diff --git a/examples/all-in-one.py b/examples/all-in-one.py index 54986036..9b7121ea 100755 --- a/examples/all-in-one.py +++ b/examples/all-in-one.py @@ -4,7 +4,7 @@ import sys import time -import ST7735 +import st7735 try: # Transitional fix for breaking change in LTR559 @@ -24,9 +24,9 @@ from enviroplus import gas logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors @@ -41,11 +41,11 @@ pms5003 = PMS5003() # Create ST7735 LCD display class -st7735 = ST7735.ST7735( +st7735 = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -57,7 +57,7 @@ HEIGHT = st7735.height # Set up canvas and font -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) font_size = 20 font = ImageFont.truetype(UserFont, font_size) @@ -77,7 +77,7 @@ def display_text(variable, data, unit): vmax = max(values[variable]) colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] # Format the variable name and value - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) for i in range(len(colours)): @@ -144,7 +144,7 @@ def get_cpu_temperature(): # One mode for each variable if mode == 0: # variable = "temperature" - unit = "C" + unit = "°C" cpu_temp = get_cpu_temperature() # Smooth out with some averaging to decrease jitter cpu_temps = cpu_temps[1:] + [cpu_temp] diff --git a/examples/combined.py b/examples/combined.py index 1ffaccb6..eaab10f3 100755 --- a/examples/combined.py +++ b/examples/combined.py @@ -4,7 +4,7 @@ import sys import time -import ST7735 +import st7735 try: # Transitional fix for breaking change in LTR559 @@ -26,9 +26,9 @@ from enviroplus import gas logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""combined.py - Displays readings from all of Enviro plus' sensors @@ -44,11 +44,11 @@ time.sleep(1.0) # Create ST7735 LCD display class -st7735 = ST7735.ST7735( +st7735 = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -60,7 +60,7 @@ HEIGHT = st7735.height # Set up canvas and font -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) font_size_small = 10 font_size_large = 20 @@ -140,7 +140,7 @@ def display_text(variable, data, unit): vmax = max(values[variable]) colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] # Format the variable name and value - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) for i in range(len(colours)): @@ -163,7 +163,7 @@ def save_data(idx, data): # Maintain length of list values[variable] = values[variable][1:] + [data] unit = units[idx] - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) @@ -178,7 +178,7 @@ def display_everything(): unit = units[i] x = x_offset + ((WIDTH // column_count) * (i // row_count)) y = y_offset + ((HEIGHT / row_count) * (i % row_count)) - message = "{}: {:.1f} {}".format(variable[:4], data_value, unit) + message = f"{variable[:4]}: {data_value:.1f} {unit}" lim = limits[i] rgb = palette[0] for j in range(len(lim)): @@ -190,9 +190,9 @@ def display_everything(): # Get the temperature of the CPU for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) + process = Popen(["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True) output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + return float(output[output.index("=") + 1:output.rindex("'")]) def main(): @@ -223,7 +223,7 @@ def main(): # One mode for each variable if mode == 0: # variable = "temperature" - unit = "C" + unit = "°C" cpu_temp = get_cpu_temperature() # Smooth out with some averaging to decrease jitter cpu_temps = cpu_temps[1:] + [cpu_temp] diff --git a/examples/compensated-temperature.py b/examples/compensated-temperature.py index 1d36140c..fb692f07 100755 --- a/examples/compensated-temperature.py +++ b/examples/compensated-temperature.py @@ -1,20 +1,15 @@ #!/usr/bin/env python3 +import logging import time from bme280 import BME280 - -try: - from smbus2 import SMBus -except ImportError: - from smbus import SMBus - -import logging +from smbus2 import SMBus logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""compensated-temperature.py - Use the CPU temperature to compensate temperature readings from the BME280 sensor. @@ -50,5 +45,5 @@ def get_cpu_temperature(): avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps)) raw_temp = bme280.get_temperature() comp_temp = raw_temp - ((avg_cpu_temp - raw_temp) / factor) - logging.info("Compensated temperature: {:05.2f} *C".format(comp_temp)) + logging.info(f"Compensated temperature: {comp_temp:05.2f} °C") time.sleep(1.0) diff --git a/examples/gas.py b/examples/gas.py index a107696a..c5fce5fc 100755 --- a/examples/gas.py +++ b/examples/gas.py @@ -6,9 +6,9 @@ from enviroplus import gas logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""gas.py - Print readings from the MICS6814 Gas sensor. diff --git a/examples/lcd.py b/examples/lcd.py index 0c0606e0..cb2af2f3 100755 --- a/examples/lcd.py +++ b/examples/lcd.py @@ -2,14 +2,14 @@ import logging -import ST7735 +import st7735 from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""lcd.py - Hello, World! example on the 0.96" LCD. @@ -18,11 +18,11 @@ """) # Create LCD class instance. -disp = ST7735.ST7735( +disp = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -35,7 +35,7 @@ HEIGHT = disp.height # New canvas to draw on. -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) # Text settings. @@ -45,7 +45,10 @@ back_colour = (0, 170, 170) message = "Hello, World!" -size_x, size_y = draw.textsize(message, font) + +x1, y1, x2, y2 = font.getbbox(message) +size_x = x2 - x1 +size_y = y2 - y1 # Calculate text position x = (WIDTH - size_x) / 2 diff --git a/examples/light.py b/examples/light.py index ead18a20..70414db3 100755 --- a/examples/light.py +++ b/examples/light.py @@ -12,9 +12,9 @@ logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""light.py - Print readings from the LTR559 Light & Proximity sensor. @@ -26,9 +26,9 @@ while True: lux = ltr559.get_lux() prox = ltr559.get_proximity() - logging.info("""Light: {:05.02f} Lux -Proximity: {:05.02f} -""".format(lux, prox)) + logging.info(f"""Light: {lux:05.02f} Lux +Proximity: {prox:05.02f} +""") time.sleep(1.0) except KeyboardInterrupt: pass diff --git a/examples/luftdaten.py b/examples/luftdaten.py index 969c9f16..ca1e4c9b 100755 --- a/examples/luftdaten.py +++ b/examples/luftdaten.py @@ -1,40 +1,35 @@ #!/usr/bin/env python3 +import logging import time from subprocess import check_output import requests -import ST7735 +import st7735 from bme280 import BME280 from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont from pms5003 import PMS5003, ChecksumMismatchError, ReadTimeoutError - -try: - from smbus2 import SMBus -except ImportError: - from smbus import SMBus - -import logging +from smbus2 import SMBus logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""luftdaten.py - Reads temperature, pressure, humidity, -#PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, -#the citizen science air quality project. +PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, +the citizen science air quality project. -#Note: you'll need to register with Luftdaten at: -#https://meine.luftdaten.info/ and enter your Raspberry Pi -#serial number that's displayed on the Enviro plus LCD along -#with the other details before the data appears on the -#Luftdaten map. +Note: you'll need to register with Luftdaten at: +https://meine.luftdaten.info/ and enter your Raspberry Pi +serial number that's displayed on the Enviro plus LCD along +with the other details before the data appears on the +Luftdaten map. -#Press Ctrl+C to exit! +Press Ctrl+C to exit! -#""") +""") bus = SMBus(1) @@ -42,11 +37,11 @@ bme280 = BME280(i2c_dev=bus) # Create LCD instance -disp = ST7735.ST7735( +disp = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -64,9 +59,9 @@ def read_values(): cpu_temp = get_cpu_temperature() raw_temp = bme280.get_temperature() comp_temp = raw_temp - ((cpu_temp - raw_temp) / comp_factor) - values["temperature"] = "{:.2f}".format(comp_temp) - values["pressure"] = "{:.2f}".format(bme280.get_pressure() * 100) - values["humidity"] = "{:.2f}".format(bme280.get_humidity()) + values["temperature"] = f"{comp_temp:.2f}" + values["pressure"] = f"{bme280.get_pressure() * 100:.2f}" + values["humidity"] = f"{bme280.get_humidity():.2f}" try: pm_values = pms5003.read() values["P2"] = str(pm_values.pm_ug_per_m3(2.5)) @@ -90,15 +85,15 @@ def get_cpu_temperature(): # Get Raspberry Pi serial number to use as ID def get_serial_number(): - with open('/proc/cpuinfo', 'r') as f: + with open("/proc/cpuinfo", "r") as f: for line in f: - if line[0:6] == 'Serial': + if line.startswith("Serial"): return line.split(":")[1].strip() # Check for Wi-Fi connection def check_wifi(): - if check_output(['hostname', '-I']): + if check_output(["hostname", "-I"]): return True else: return False @@ -110,10 +105,12 @@ def display_status(): text_colour = (255, 255, 255) back_colour = (0, 170, 170) if check_wifi() else (85, 15, 15) id = get_serial_number() - message = "{}\nWi-Fi: {}".format(id, wifi_status) - img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) + message = f"{id}\nWi-Fi: {wifi_status}" + img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) - size_x, size_y = draw.textsize(message, font) + x1, y1, x2, y2 = font.getbbox(message) + size_x = x2 - x1 + size_y = y2 - y1 x = (WIDTH - size_x) / 2 y = (HEIGHT / 2) - (size_y / 2) draw.rectangle((0, 0, 160, 80), back_colour) @@ -147,11 +144,11 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning('Sensor.Community (Luftdaten) PM Connection Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) PM Connection Error: {e}") except requests.exceptions.Timeout as e: - logging.warning('Sensor.Community (Luftdaten) PM Timeout Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) PM Timeout Error: {e}") except requests.exceptions.RequestException as e: - logging.warning('Sensor.Community (Luftdaten) PM Request Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) PM Request Error: {e}") try: resp_bmp = requests.post( @@ -169,17 +166,17 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning('Sensor.Community (Luftdaten) Climate Connection Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) Climate Connection Error: {e}") except requests.exceptions.Timeout as e: - logging.warning('Sensor.Community (Luftdaten) Climate Timeout Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) Climate Timeout Error: {e}") except requests.exceptions.RequestException as e: - logging.warning('Sensor.Community (Luftdaten) Climate Request Error: {}'.format(e)) + logging.warning(f"Sensor.Community (Luftdaten) Climate Request Error: {e}") if resp_pm is not None and resp_bmp is not None: if resp_pm.ok and resp_bmp.ok: return True else: - logging.warning('Luftdaten Error. PM: {}, Climate: {}'.format(resp_pm.reason, resp_bmp.reason)) + logging.warning(f"Luftdaten Error. PM: {resp_pm.reason}, Climate: {resp_bmp.reason}") return False else: return False @@ -200,8 +197,9 @@ def send_to_luftdaten(values, id): font = ImageFont.truetype(UserFont, font_size) # Log Raspberry Pi serial and Wi-Fi status -logging.info("Raspberry Pi serial: {}".format(get_serial_number())) -logging.info("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) +logging.info(f"Raspberry Pi serial: {get_serial_number()}") +wifi_status = "connected" if check_wifi() else "disconnected" +logging.info(f"Wi-Fi: {wifi_status}\n") time_since_update = 0 update_time = time.time() @@ -220,4 +218,4 @@ def send_to_luftdaten(values, id): logging.warning("Luftdaten Response: Failed") display_status() except Exception as e: - logging.warning('Main Loop Exception: {}'.format(e)) + logging.warning(f"Main Loop Exception: {e}") diff --git a/examples/luftdaten_combined.py b/examples/luftdaten_combined.py index 978ce283..293e414d 100644 --- a/examples/luftdaten_combined.py +++ b/examples/luftdaten_combined.py @@ -4,18 +4,15 @@ from subprocess import PIPE, Popen, check_output import requests -import ST7735 +import st7735 from bme280 import BME280 from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont from pms5003 import PMS5003, ReadTimeoutError +from smbus2 import SMBus from enviroplus import gas -try: - from smbus2 import SMBus -except ImportError: - from smbus import SMBus try: # Transitional fix for breaking change in LTR559 from ltr559 import LTR559 @@ -48,9 +45,9 @@ """) logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info(""" """) bus = SMBus(1) @@ -120,9 +117,9 @@ # Read values from BME280 and PMS5003 and return as dict def read_values(comp_temp, mod_press, raw_humid, raw_pm25, raw_pm10): values = {} - values["temperature"] = "{:.2f}".format(comp_temp) - values["pressure"] = "{:.2f}".format(mod_press) - values["humidity"] = "{:.2f}".format(raw_humid) + values["temperature"] = f"{comp_temp:.2f}" + values["pressure"] = f"{mod_press:.2f}" + values["humidity"] = f"{raw_humid:.2f}" values["P2"] = str(raw_pm25) values["P1"] = str(raw_pm10) return values @@ -130,34 +127,34 @@ def read_values(comp_temp, mod_press, raw_humid, raw_pm25, raw_pm10): # Get CPU temperature to use for compensation def get_cpu_temperature(): - process = Popen(['vcgencmd', 'measure_temp'], + process = Popen(["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True) output, _error = process.communicate() - return float(output[output.index('=') + 1:output.rindex("'")]) + return float(output[output.index("=") + 1:output.rindex("'")]) # Get Raspberry Pi serial number to use as ID def get_serial_number(): - with open('/proc/cpuinfo', 'r') as f: + with open("/proc/cpuinfo", "r") as f: for line in f: - if line[0:6] == 'Serial': + if line.startswith("Serial"): return line.split(":")[1].strip() # Check for Wi-Fi connection def check_wifi(): - if check_output(['hostname', '-I']): + if check_output(["hostname", "-I"]): return True else: return False # Create ST7735 LCD display class -st7735 = ST7735.ST7735( +st7735 = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -169,7 +166,7 @@ def check_wifi(): HEIGHT = st7735.height # Set up canvas and font -img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) +img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) font_size_small = 10 font_size_large = 20 @@ -190,7 +187,7 @@ def save_data(idx, data): # Maintain length of list values_lcd[variable] = values_lcd[variable][1:] + [data] unit = units[idx] - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) @@ -204,7 +201,7 @@ def display_text(variable, data, unit): colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values_lcd[variable]] # Format the variable name and value - message = "{}: {:.1f} {}".format(variable[:4], data, unit) + message = f"{variable[:4]}: {data:.1f} {unit}" logging.info(message) draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) for i in range(len(colours)): @@ -235,7 +232,7 @@ def display_everything(): unit = units[i] x = x_offset + ((WIDTH // column_count) * (i // row_count)) y = y_offset + ((HEIGHT / row_count) * (i % row_count)) - message = "{}: {:.1f} {}".format(variable[:4], data_value, unit) + message = f"{variable[:4]}: {data_value:.1f} {unit}" lim = limits[i] rgb = palette[0] for j in range(len(lim)): @@ -312,8 +309,9 @@ def send_to_luftdaten(values, id): cpu_temps = [get_cpu_temperature()] * 5 # Display Raspberry Pi serial and Wi-Fi status -print("Raspberry Pi serial: {}".format(get_serial_number())) -print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) +print(f"Raspberry Pi serial: {get_serial_number()}") +wifi_status = "connected" if check_wifi() else "disconnected" +print(f"Wi-Fi: {wifi_status}\n") time_since_update = 0 update_time = time.time() @@ -350,7 +348,8 @@ def send_to_luftdaten(values, id): raw_humid, raw_pm25, raw_pm10) resp = send_to_luftdaten(values, id) update_time = curtime - print("Response: {}\n".format("ok" if resp else "failed")) + status = "ok" if resp else "failed" + print(f"Response: {status}\n") # Now comes the combined.py functionality: # If the proximity crosses the threshold, toggle the mode diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 16e8f0df..7238d3b0 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -9,7 +9,7 @@ import ssl import time -import ST7735 +import st7735 from bme280 import BME280 from pms5003 import PMS5003, ReadTimeoutError, SerialTimeoutError @@ -133,12 +133,12 @@ def display_status(disp, mqtt_broker): text_colour = (255, 255, 255) back_colour = (0, 170, 170) if check_wifi() else (85, 15, 15) device_serial_number = get_serial_number() - message = "{}\nWi-Fi: {}\nmqtt-broker: {}".format( - device_serial_number, wifi_status, mqtt_broker - ) + message = f"{device_serial_number}\nWi-Fi: {wifi_status}\nmqtt-broker: {mqtt_broker}" img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) - size_x, size_y = draw.textsize(message, font) + x1, y1, x2, y2 = font.getbbox(message) + size_x = x2 - x1 + size_y = y2 - y1 x = (WIDTH - size_x) / 2 y = (HEIGHT / 2) - (size_y / 2) draw.rectangle((0, 0, 160, 80), back_colour) @@ -174,7 +174,7 @@ def main(): parser.add_argument( "--tls", default=DEFAULT_TLS_MODE, - action='store_true', + action="store_true", help="enable TLS" ) parser.add_argument( @@ -231,8 +231,13 @@ def main(): bme280 = BME280(i2c_dev=bus) # Create LCD instance - disp = ST7735.ST7735( - port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=10000000 + disp = st7735.ST7735( + port=0, + cs=1, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + rotation=270, + spi_speed_hz=10000000 ) # Initialize display @@ -249,9 +254,10 @@ def main(): print("No PMS5003 sensor connected") # Display Raspberry Pi serial and Wi-Fi status - print("RPi serial: {}".format(device_serial_number)) - print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) - print("MQTT broker IP: {}".format(args.broker)) + print(f"RPi serial: {device_serial_number}") + wifi_status = "connected" if check_wifi() else "disconnected" + print(f"Wi-Fi: {wifi_status}\n") + print(f"MQTT broker IP: {args.broker}") # Set an initial update time update_time = time.time() diff --git a/examples/noise-amps-at-freqs.py b/examples/noise-amps-at-freqs.py index ddf1a315..825f53cf 100755 --- a/examples/noise-amps-at-freqs.py +++ b/examples/noise-amps-at-freqs.py @@ -1,4 +1,4 @@ -import ST7735 +import st7735 from PIL import Image, ImageDraw from enviroplus.noise import Noise @@ -15,16 +15,18 @@ noise = Noise() -disp = ST7735.ST7735( +disp = st7735.ST7735( port=0, - cs=ST7735.BG_SPI_CS_FRONT, - dc=9, - backlight=12, - rotation=90) + cs=1, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + rotation=270, + spi_speed_hz=10000000 +) disp.begin() -img = Image.new('RGB', (disp.width, disp.height), color=(0, 0, 0)) +img = Image.new("RGB", (disp.width, disp.height), color=(0, 0, 0)) draw = ImageDraw.Draw(img) diff --git a/examples/noise-profile.py b/examples/noise-profile.py index c37ba35c..17ada829 100755 --- a/examples/noise-profile.py +++ b/examples/noise-profile.py @@ -1,4 +1,4 @@ -import ST7735 +import st7735 from PIL import Image, ImageDraw from enviroplus.noise import Noise @@ -13,16 +13,18 @@ noise = Noise() -disp = ST7735.ST7735( +disp = st7735.ST7735( port=0, - cs=ST7735.BG_SPI_CS_FRONT, - dc=9, - backlight=12, - rotation=90) + cs=1, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + rotation=270, + spi_speed_hz=10000000 +) disp.begin() -img = Image.new('RGB', (disp.width, disp.height), color=(0, 0, 0)) +img = Image.new("RGB", (disp.width, disp.height), color=(0, 0, 0)) draw = ImageDraw.Draw(img) diff --git a/examples/particulates.py b/examples/particulates.py index 88d30e0c..6ecaf707 100755 --- a/examples/particulates.py +++ b/examples/particulates.py @@ -6,9 +6,9 @@ from pms5003 import PMS5003, ReadTimeoutError logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""particulates.py - Print readings from the PMS5003 particulate sensor. diff --git a/examples/weather-and-light.py b/examples/weather-and-light.py index 12b43def..efbd2a88 100755 --- a/examples/weather-and-light.py +++ b/examples/weather-and-light.py @@ -8,18 +8,14 @@ import numpy import pytz -import ST7735 +import st7735 from astral.geocoder import database, lookup from astral.sun import sun from bme280 import BME280 from fonts.ttf import RobotoMedium as UserFont from ltr559 import LTR559 from PIL import Image, ImageDraw, ImageFilter, ImageFont - -try: - from smbus2 import SMBus -except ImportError: - from smbus import SMBus +from smbus2 import SMBus def calculate_y_pos(x, centre): @@ -44,7 +40,7 @@ def circle_coordinates(x, y, radius): def map_colour(x, centre, start_hue, end_hue, day): """Given an x coordinate and a centre point, a start and end hue (in degrees), and a Boolean for day or night (day is True, night False), calculate a colour - hue representing the 'colour' of that time of day.""" + hue representing the "colour" of that time of day.""" start_hue = start_hue / 360 # Rescale to between 0 and 1 end_hue = end_hue / 360 @@ -81,7 +77,7 @@ def x_from_sun_moon_time(progress, period, x_range): def sun_moon_time(city_name, time_zone): """Calculate the progress through the current sun/moon period (i.e day or - night) from the last sunrise or sunset, given a datetime object 't'.""" + night) from the last sunrise or sunset, given a datetime object "t".""" city = lookup(city_name, database()) @@ -137,11 +133,11 @@ def draw_background(progress, period, day): # x-coordinate for sun/moon x = x_from_sun_moon_time(progress, period, WIDTH) - # If it's day, then move right to left + # If it"s day, then move right to left if day: x = WIDTH - x - # Calculate position on sun/moon's curve + # Calculate position on sun/moon"s curve centre = WIDTH / 2 y = calculate_y_pos(x, centre) @@ -149,11 +145,11 @@ def draw_background(progress, period, day): background = map_colour(x, 80, mid_hue, day_hue, day) # New image for background colour - img = Image.new('RGBA', (WIDTH, HEIGHT), color=background) + img = Image.new("RGBA", (WIDTH, HEIGHT), color=background) # draw = ImageDraw.Draw(img) # New image for sun/moon overlay - overlay = Image.new('RGBA', (WIDTH, HEIGHT), color=(0, 0, 0, 0)) + overlay = Image.new("RGBA", (WIDTH, HEIGHT), color=(0, 0, 0, 0)) overlay_draw = ImageDraw.Draw(overlay) # Draw the sun/moon @@ -166,9 +162,19 @@ def draw_background(progress, period, day): return composite +def text_width(font, text): + x1, y1, x2, y2 = font.getbbox(text) + return x2 - x1 + + +def text_size(font, text): + x1, y1, x2, y2 = font.getbbox(text) + return x2 - x1, y2 - y1 + + def overlay_text(img, position, text, font, align_right=False, rectangle=False): draw = ImageDraw.Draw(img) - w, h = font.getsize(text) + w, h = text_size(font, text) if align_right: x, y = position x -= w @@ -179,7 +185,7 @@ def overlay_text(img, position, text, font, align_right=False, rectangle=False): position = (x, y) border = 1 rect = (x - border, y, x + w, y + h + border) - rect_img = Image.new('RGBA', (WIDTH, HEIGHT), color=(0, 0, 0, 0)) + rect_img = Image.new("RGBA", (WIDTH, HEIGHT), color=(0, 0, 0, 0)) rect_draw = ImageDraw.Draw(rect_img) rect_draw.rectangle(rect, (255, 255, 255)) rect_draw.text(position, text, font=font, fill=(0, 0, 0, 0)) @@ -287,11 +293,11 @@ def describe_light(light): # Initialise the LCD -disp = ST7735.ST7735( +disp = st7735.ST7735( port=0, cs=1, - dc=9, - backlight=12, + dc="PIN21", # "GPIO9" on a Raspberry Pi 4 + backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 rotation=270, spi_speed_hz=10000000 ) @@ -352,7 +358,7 @@ def describe_light(light): # Time. time_elapsed = time.time() - start_time - date_string = local_dt.strftime("%d %b %y").lstrip('0') + date_string = local_dt.strftime("%d %b %y").lstrip("0") time_string = local_dt.strftime("%H:%M") img = overlay_text(background, (0 + margin, 0 + margin), time_string, font_lg) img = overlay_text(img, (WIDTH - margin, 0 + margin), date_string, font_lg, align_right=True) @@ -378,7 +384,7 @@ def describe_light(light): temp_string = f"{corr_temperature:.0f}°C" img = overlay_text(img, (68, 18), temp_string, font_lg, align_right=True) - spacing = font_lg.getsize(temp_string)[1] + 1 + spacing = text_width(font_lg, temp_string) + 1 if min_temp is not None and max_temp is not None: range_string = f"{min_temp:.0f}-{max_temp:.0f}" else: @@ -392,7 +398,7 @@ def describe_light(light): corr_humidity = correct_humidity(humidity, temperature, corr_temperature) humidity_string = f"{corr_humidity:.0f}%" img = overlay_text(img, (68, 48), humidity_string, font_lg, align_right=True) - spacing = font_lg.getsize(humidity_string)[1] + 1 + spacing = text_width(font_lg, humidity_string) + 1 humidity_desc = describe_humidity(corr_humidity).upper() img = overlay_text(img, (68, 48 + spacing), humidity_desc, font_sm, align_right=True, rectangle=True) humidity_icon = Image.open(f"{path}/icons/humidity-{humidity_desc.lower()}.png") @@ -402,7 +408,7 @@ def describe_light(light): light = ltr559.get_lux() light_string = f"{int(light):,}" img = overlay_text(img, (WIDTH - margin, 18), light_string, font_lg, align_right=True) - spacing = font_lg.getsize(light_string.replace(",", ""))[1] + 1 + spacing = text_width(font_lg, light_string.replace(",", "")) + 1 light_desc = describe_light(light).upper() img = overlay_text(img, (WIDTH - margin - 1, 18 + spacing), light_desc, font_sm, align_right=True, rectangle=True) light_icon = Image.open(f"{path}/icons/bulb-{light_desc.lower()}.png") @@ -415,7 +421,7 @@ def describe_light(light): pressure_string = f"{int(mean_pressure):,} {trend}" img = overlay_text(img, (WIDTH - margin, 48), pressure_string, font_lg, align_right=True) pressure_desc = describe_pressure(mean_pressure).upper() - spacing = font_lg.getsize(pressure_string.replace(",", ""))[1] + 1 + spacing = text_width(font_lg, pressure_string.replace(",", "")) + 1 img = overlay_text(img, (WIDTH - margin - 1, 48 + spacing), pressure_desc, font_sm, align_right=True, rectangle=True) pressure_icon = Image.open(f"{path}/icons/weather-{pressure_desc.lower()}.png") img.paste(pressure_icon, (80, 48), mask=pressure_icon) diff --git a/examples/weather.py b/examples/weather.py index 23a6a86e..0b671d31 100755 --- a/examples/weather.py +++ b/examples/weather.py @@ -1,20 +1,15 @@ #!/usr/bin/env python3 +import logging import time from bme280 import BME280 - -try: - from smbus2 import SMBus -except ImportError: - from smbus import SMBus - -import logging +from smbus2 import SMBus logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', + format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') + datefmt="%Y-%m-%d %H:%M:%S") logging.info("""weather.py - Print readings from the BME280 weather sensor. @@ -29,8 +24,8 @@ temperature = bme280.get_temperature() pressure = bme280.get_pressure() humidity = bme280.get_humidity() - logging.info("""Temperature: {:05.2f} *C -Pressure: {:05.2f} hPa -Relative humidity: {:05.2f} % -""".format(temperature, pressure, humidity)) + logging.info(f"""Temperature: {temperature:05.2f} °C +Pressure: {pressure:05.2f} hPa +Relative humidity: {humidity:05.2f} % +""") time.sleep(1) diff --git a/pyproject.toml b/pyproject.toml index bf030cc2..e7346f14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,9 @@ skip = """ [tool.isort] line_length = 200 +[tool.black] +line-length = 200 + [tool.check-manifest] ignore = [ '.stickler.yml', diff --git a/tests/conftest.py b/tests/conftest.py index a7b3b333..6ad5557c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,96 +21,96 @@ def __init__(self, i2c_bus): self.regs[0x00:0x01] = 0x0f, 0x80 -@pytest.fixture(scope='function', autouse=True) +@pytest.fixture(scope="function", autouse=True) def cleanup(): yield None - try: - del sys.modules['enviroplus'] - except KeyError: - pass - try: - del sys.modules['enviroplus.noise'] - except KeyError: - pass - try: - del sys.modules['enviroplus.gas'] - except KeyError: - pass - - -@pytest.fixture(scope='function', autouse=False) -def GPIO(): - """Mock RPi.GPIO module.""" - GPIO = mock.MagicMock() - # Fudge for Python < 37 (possibly earlier) - sys.modules['RPi'] = mock.Mock() - sys.modules['RPi'].GPIO = GPIO - sys.modules['RPi.GPIO'] = GPIO - yield GPIO - del sys.modules['RPi'] - del sys.modules['RPi.GPIO'] - - -@pytest.fixture(scope='function', autouse=False) + modules = "enviroplus", "enviroplus.noise", "enviroplus.gas", "ads1015", "i2cdevice" + for module in modules: + try: + del sys.modules[module] + except KeyError: + pass + + +@pytest.fixture(scope="function", autouse=False) +def gpiod(): + sys.modules["gpiod"] = mock.Mock() + sys.modules["gpiod.line"] = mock.Mock() + yield sys.modules["gpiod"] + del sys.modules["gpiod.line"] + del sys.modules["gpiod"] + + +@pytest.fixture(scope="function", autouse=False) +def gpiodevice(): + gpiodevice = mock.Mock() + gpiodevice.get_pins_for_platform.return_value = [(mock.Mock(), 0)] + + sys.modules["gpiodevice"] = gpiodevice + yield gpiodevice + del sys.modules["gpiodevice"] + + +@pytest.fixture(scope="function", autouse=False) def spidev(): """Mock spidev module.""" spidev = mock.MagicMock() - sys.modules['spidev'] = spidev + sys.modules["spidev"] = spidev yield spidev - del sys.modules['spidev'] + del sys.modules["spidev"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def smbus(): - """Mock smbus module.""" + """Mock smbus2 module.""" smbus = mock.MagicMock() smbus.SMBus = SMBusFakeDevice - sys.modules['smbus'] = smbus + sys.modules["smbus2"] = smbus yield smbus - del sys.modules['smbus'] + del sys.modules["smbus2"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def smbus_notimeout(): - """Mock smbus module.""" + """Mock smbus2 module.""" smbus = mock.MagicMock() smbus.SMBus = SMBusFakeDeviceNoTimeout - sys.modules['smbus'] = smbus + sys.modules["smbus2"] = smbus yield smbus - del sys.modules['smbus'] + del sys.modules["smbus2"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def mocksmbus(): - """Mock smbus module.""" + """Mock smbus2 module.""" smbus = mock.MagicMock() - sys.modules['smbus'] = smbus + sys.modules["smbus2"] = smbus yield smbus - del sys.modules['smbus'] + del sys.modules["smbus2"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def atexit(): """Mock atexit module.""" atexit = mock.MagicMock() - sys.modules['atexit'] = atexit + sys.modules["atexit"] = atexit yield atexit - del sys.modules['atexit'] + del sys.modules["atexit"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def sounddevice(): """Mock sounddevice module.""" sounddevice = mock.MagicMock() - sys.modules['sounddevice'] = sounddevice + sys.modules["sounddevice"] = sounddevice yield sounddevice - del sys.modules['sounddevice'] + del sys.modules["sounddevice"] -@pytest.fixture(scope='function', autouse=False) +@pytest.fixture(scope="function", autouse=False) def numpy(): """Mock numpy module.""" numpy = mock.MagicMock() - sys.modules['numpy'] = numpy + sys.modules["numpy"] = numpy yield numpy - del sys.modules['numpy'] + del sys.modules["numpy"] diff --git a/tests/test_noise.py b/tests/test_noise.py index 4949a3d3..a5eb7da0 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -17,7 +17,7 @@ def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy): (501, 1000) ]) - sounddevice.rec.assert_called_with(0.1 * 16000, device='adau7002', samplerate=16000, blocking=True, channels=1, dtype='float64') + sounddevice.rec.assert_called_with(0.1 * 16000, device="adau7002", samplerate=16000, blocking=True, channels=1, dtype="float64") def test_noise_get_noise_profile(sounddevice, numpy): @@ -32,7 +32,7 @@ def test_noise_get_noise_profile(sounddevice, numpy): mid=0.36, high=None) - sounddevice.rec.assert_called_with(0.1 * 16000, device='adau7002', samplerate=16000, blocking=True, channels=1, dtype='float64') + sounddevice.rec.assert_called_with(0.1 * 16000, device="adau7002", samplerate=16000, blocking=True, channels=1, dtype="float64") assert amp_total == 10.0 diff --git a/tests/test_setup.py b/tests/test_setup.py index ac254e02..fa7fb939 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,14 +1,14 @@ import pytest -def test_gas_setup(GPIO, smbus): +def test_gas_setup(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False gas.setup() gas.setup() -def test_gas_unavailable(GPIO, mocksmbus): +def test_gas_unavailable(gpiod, gpiodevice, mocksmbus): from enviroplus import gas mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh no!") gas._is_setup = False @@ -18,13 +18,13 @@ def test_gas_unavailable(GPIO, mocksmbus): gas.read_all() -def test_gas_available(GPIO, smbus_notimeout): +def test_gas_available(gpiod, gpiodevice, smbus_notimeout): from enviroplus import gas gas._is_setup = False assert gas.available() is True -def test_gas_read_all(GPIO, smbus): +def test_gas_read_all(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False result = gas.read_all() @@ -41,7 +41,7 @@ def test_gas_read_all(GPIO, smbus): assert "Oxidising" in str(result) -def test_gas_read_each(GPIO, smbus): +def test_gas_read_each(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False @@ -50,7 +50,7 @@ def test_gas_read_each(GPIO, smbus): assert int(gas.read_nh3()) == 16813 -def test_gas_read_adc(GPIO, smbus): +def test_gas_read_adc(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False @@ -59,7 +59,7 @@ def test_gas_read_adc(GPIO, smbus): assert gas.read_adc() == 0.255 -def test_gas_read_adc_default_gain(GPIO, smbus): +def test_gas_read_adc_default_gain(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False @@ -68,18 +68,19 @@ def test_gas_read_adc_default_gain(GPIO, smbus): assert gas.read_adc() == 0.765 -def test_gas_read_adc_str(GPIO, smbus): +def test_gas_read_adc_str(gpiod, gpiodevice, smbus): from enviroplus import gas gas._is_setup = False gas.enable_adc(True) gas.set_adc_gain(2.048) - assert 'ADC' in str(gas.read_all()) + assert "ADC" in str(gas.read_all()) -def test_gas_cleanup(GPIO, smbus): +def test_gas_cleanup(gpiod, gpiodevice, smbus): from enviroplus import gas gas.cleanup() - GPIO.output.assert_called_with(gas.MICS6814_HEATER_PIN, 0) + gas.setup() + gas.cleanup() From f6ee18bc0f4286dfe0e9dd2376d22264821de6cd Mon Sep 17 00:00:00 2001 From: "Michael (MicroD)" <72285408+michaeldufault@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:19:58 +0000 Subject: [PATCH 44/69] Update Luftdaten examples to Sensor.Community Co-authored-by: Phil Howard --- examples/{luftdaten.py => sensorcommunity.py} | 40 +++++++++---------- ...ombined.py => sensorcommunity_combined.py} | 28 ++++++------- 2 files changed, 34 insertions(+), 34 deletions(-) rename examples/{luftdaten.py => sensorcommunity.py} (82%) rename examples/{luftdaten_combined.py => sensorcommunity_combined.py} (94%) diff --git a/examples/luftdaten.py b/examples/sensorcommunity.py similarity index 82% rename from examples/luftdaten.py rename to examples/sensorcommunity.py index ca1e4c9b..7128ad72 100755 --- a/examples/luftdaten.py +++ b/examples/sensorcommunity.py @@ -17,15 +17,15 @@ level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S") -logging.info("""luftdaten.py - Reads temperature, pressure, humidity, -PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, +logging.info("""sensorcommunity.py - Reads temperature, pressure, humidity, +PM2.5, and PM10 from Enviro plus and sends data to Sensor.Community, the citizen science air quality project. -Note: you'll need to register with Luftdaten at: -https://meine.luftdaten.info/ and enter your Raspberry Pi +Note: you'll need to register with Sensor.Community at: +https://devices.sensor.community/ and enter your Raspberry Pi serial number that's displayed on the Enviro plus LCD along with the other details before the data appears on the -Luftdaten map. +Sensor.Community map. Press Ctrl+C to exit! @@ -118,7 +118,7 @@ def display_status(): disp.display(img) -def send_to_luftdaten(values, id): +def send_to_sensorcommunity(values, id): pm_values = dict(i for i in values.items() if i[0].startswith("P")) temp_values = dict(i for i in values.items() if not i[0].startswith("P")) @@ -132,7 +132,7 @@ def send_to_luftdaten(values, id): resp_pm = requests.post( "https://api.sensor.community/v1/push-sensor-data/", json={ - "software_version": "enviro-plus 0.0.1", + "software_version": "enviro-plus 1.0.0", "sensordatavalues": pm_values_json }, headers={ @@ -144,17 +144,17 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning(f"Sensor.Community (Luftdaten) PM Connection Error: {e}") + logging.warning(f"Sensor.Community PM Connection Error: {e}") except requests.exceptions.Timeout as e: - logging.warning(f"Sensor.Community (Luftdaten) PM Timeout Error: {e}") + logging.warning(f"Sensor.Community PM Timeout Error: {e}") except requests.exceptions.RequestException as e: - logging.warning(f"Sensor.Community (Luftdaten) PM Request Error: {e}") + logging.warning(f"Sensor.Community PM Request Error: {e}") try: resp_bmp = requests.post( "https://api.sensor.community/v1/push-sensor-data/", json={ - "software_version": "enviro-plus 0.0.1", + "software_version": "enviro-plus 1.0.0", "sensordatavalues": temp_values_json }, headers={ @@ -166,17 +166,17 @@ def send_to_luftdaten(values, id): timeout=5 ) except requests.exceptions.ConnectionError as e: - logging.warning(f"Sensor.Community (Luftdaten) Climate Connection Error: {e}") + logging.warning(f"Sensor.Community Climate Connection Error: {e}") except requests.exceptions.Timeout as e: - logging.warning(f"Sensor.Community (Luftdaten) Climate Timeout Error: {e}") + logging.warning(f"Sensor.Community Climate Timeout Error: {e}") except requests.exceptions.RequestException as e: - logging.warning(f"Sensor.Community (Luftdaten) Climate Request Error: {e}") + logging.warning(f"Sensor.Community Climate Request Error: {e}") if resp_pm is not None and resp_bmp is not None: if resp_pm.ok and resp_bmp.ok: return True else: - logging.warning(f"Luftdaten Error. PM: {resp_pm.reason}, Climate: {resp_bmp.reason}") + logging.warning(f"Sensor.Community Error. PM: {resp_pm.reason}, Climate: {resp_bmp.reason}") return False else: return False @@ -185,7 +185,7 @@ def send_to_luftdaten(values, id): # Compensation factor for temperature comp_factor = 2.25 -# Raspberry Pi ID to send to Luftdaten +# Raspberry Pi ID to send to Sensor.Community id = "raspi-" + get_serial_number() # Width and height to calculate text position @@ -204,7 +204,7 @@ def send_to_luftdaten(values, id): time_since_update = 0 update_time = time.time() -# Main loop to read data, display, and send to Luftdaten +# Main loop to read data, display, and send to Sensor.Community while True: try: values = read_values() @@ -212,10 +212,10 @@ def send_to_luftdaten(values, id): if time_since_update > 145: logging.info(values) update_time = time.time() - if send_to_luftdaten(values, id): - logging.info("Luftdaten Response: OK") + if send_to_sensorcommunity(values, id): + logging.info("Sensor.Community Response: OK") else: - logging.warning("Luftdaten Response: Failed") + logging.warning("Sensor.Community Response: Failed") display_status() except Exception as e: logging.warning(f"Main Loop Exception: {e}") diff --git a/examples/luftdaten_combined.py b/examples/sensorcommunity_combined.py similarity index 94% rename from examples/luftdaten_combined.py rename to examples/sensorcommunity_combined.py index 293e414d..8dac7232 100644 --- a/examples/luftdaten_combined.py +++ b/examples/sensorcommunity_combined.py @@ -20,18 +20,18 @@ except ImportError: import ltr559 -print("""luftdaten_combined.py - This combines the functionality of luftdaten.py and combined.py +print("""sensorcommunity_combined.py - This combines the functionality of sensorcommunity.py and combined.py ================================================================================================ -Luftdaten INFO +Sensor.Community INFO Reads temperature, pressure, humidity, -PM2.5, and PM10 from Enviro plus and sends data to Luftdaten, +PM2.5, and PM10 from Enviro plus and sends data to Sensor.Community, the citizen science air quality project. -Note: you'll need to register with Luftdaten at: -https://meine.luftdaten.info/ and enter your Raspberry Pi +Note: you'll need to register with Sensor.Community at: +https://devices.sensor.community/ and enter your Raspberry Pi serial number that's displayed on the Enviro plus LCD along with the other details before the data appears on the -Luftdaten map. +Sensor.Community map. Press Ctrl+C to exit! @@ -242,7 +242,7 @@ def display_everything(): st7735.display(img) -def send_to_luftdaten(values, id): +def send_to_sensorcommunity(values, id): pm_values = dict(i for i in values.items() if i[0].startswith("P")) temp_values = dict(i for i in values.items() if not i[0].startswith("P")) @@ -252,9 +252,9 @@ def send_to_luftdaten(values, id): for key, val in temp_values.items()] resp_1 = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", + "https://api.sensor.community/v1/push-sensor-data/", json={ - "software_version": "enviro-plus 0.0.1", + "software_version": "enviro-plus 1.0.0", "sensordatavalues": pm_values_json }, headers={ @@ -266,9 +266,9 @@ def send_to_luftdaten(values, id): ) resp_2 = requests.post( - "https://api.luftdaten.info/v1/push-sensor-data/", + "https://api.sensor.community/v1/push-sensor-data/", json={ - "software_version": "enviro-plus 0.0.1", + "software_version": "enviro-plus 1.0.0", "sensordatavalues": temp_values_json }, headers={ @@ -288,7 +288,7 @@ def send_to_luftdaten(values, id): # Compensation factor for temperature comp_factor = 1 -# Raspberry Pi ID to send to Luftdaten +# Raspberry Pi ID to send to Sensor.Community id = "raspi-" + get_serial_number() @@ -317,7 +317,7 @@ def send_to_luftdaten(values, id): update_time = time.time() cpu_temps_len = float(len(cpu_temps)) -# Main loop to read data, display, and send to Luftdaten +# Main loop to read data, display, and send to Sensor.Community while True: try: curtime = time.time() @@ -346,7 +346,7 @@ def send_to_luftdaten(values, id): if time_since_update > 145: values = read_values(comp_temp, raw_press*100, raw_humid, raw_pm25, raw_pm10) - resp = send_to_luftdaten(values, id) + resp = send_to_sensorcommunity(values, id) update_time = curtime status = "ok" if resp else "failed" print(f"Response: {status}\n") From 11c218874f7cf81ad4898557b07d49a3ea72686d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 15 Nov 2023 13:49:21 +0000 Subject: [PATCH 45/69] Fix for config.txt location. --- check.sh | 1 + install.sh | 64 +++++++++++++++++++++++++++++++++----------------- pyproject.toml | 15 ++++-------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/check.sh b/check.sh index cbb15653..4395d89c 100755 --- a/check.sh +++ b/check.sh @@ -6,6 +6,7 @@ NOPOST=$1 LIBRARY_NAME=`hatch project metadata name` LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'` POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'` +TERM=${TERM:="xterm-256color"} success() { echo -e "$(tput setaf 2)$1$(tput sgr0)" diff --git a/install.sh b/install.sh index bee710c7..38f19e9a 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,13 @@ #!/bin/bash LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` -CONFIG=/boot/config.txt +CONFIG_FILE=config.txt +CONFIG_DIR="/boot/firmware" DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` CONFIG_BACKUP=false APT_HAS_UPDATED=false RESOURCES_TOP_DIR=$HOME/Pimoroni -VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh -VENV_DIR=$RESOURCES_TOP_DIR/venv +VENV_BASH_SNIPPET=$RESOURCES_DIR/auto_venv.sh +VENV_DIR=$HOME/.virtualenvs/pimoroni WD=`pwd` USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() @@ -14,6 +15,7 @@ FORCE=false UNSTABLE=false PYTHON="python" + user_check() { if [ $(id -u) -eq 0 ]; then printf "Script should not be run as root. Try './install.sh'\n" @@ -55,19 +57,35 @@ warning() { echo -e "$(tput setaf 1)$1$(tput sgr0)" } +find_config() { + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then + CONFIG_DIR="/boot" + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE"]; then + warning "Could not find $CONFIG_FILE!" + exit 1 + fi + else + if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then + warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE" + warning "You might want to fix this!" + fi + fi + inform "Using $CONFIG_FILE in $CONFIG_DIR" +} + venv_bash_snippet() { if [ ! -f $VENV_BASH_SNIPPET ]; then cat << EOF > $VENV_BASH_SNIPPET -# Add \`source $RESOURCES_TOP_DIR/auto_venv.sh\` to your ~/.bashrc to activate +# Add `source $RESOURCES_DIR/auto_venv.sh` to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! -PY_ENV_DIR=~/Pimoroni/venv -if [ ! -f \$PY_ENV_DIR/bin/activate ]; then - printf "Creating user Python environment in \$PY_ENV_DIR, please wait...\n" - mkdir -p \$PY_ENV_DIR - python3 -m venv --system-site-packages --prompt Pimoroni \$PY_ENV_DIR +VENV_DIR="$VENV_DIR" +if [ ! -f \$VENV_DIR/bin/activate ]; then + printf "Creating user Python environment in \$VENV_DIR, please wait...\n" + mkdir -p \$VENV_DIR + python3 -m venv --system-site-packages \$VENV_DIR fi printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n" -source \$PY_ENV_DIR/bin/activate +source \$VENV_DIR/bin/activate EOF fi } @@ -80,7 +98,7 @@ venv_check() { if [ ! -f $VENV_DIR/bin/activate ]; then inform "Creating virtual Python environment in $VENV_DIR, please wait...\n" mkdir -p $VENV_DIR - /usr/bin/python3 -m venv $VENV_DIR --system-site-packages --prompt Pimoroni + /usr/bin/python3 -m venv $VENV_DIR --system-site-packages venv_bash_snippet else inform "Found existing virtual Python environment in $VENV_DIR\n" @@ -99,12 +117,12 @@ function do_config_backup { if [ ! $CONFIG_BACKUP == true ]; then CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" - inform "Backing up $CONFIG to /boot/$FILENAME\n" - sudo cp $CONFIG /boot/$FILENAME + inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n" + sudo cp $CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME + cp $CONFIG_DIR/$CONFIG_FILE $RESOURCES_TOP_DIR/config-backups/$FILENAME if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER + echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> $UNINSTALLER fi fi } @@ -185,7 +203,7 @@ pip_pkg_install toml CONFIG_VARS=`$PYTHON - < Date: Tue, 21 Nov 2023 16:01:41 +0000 Subject: [PATCH 46/69] Fix VENV_DIR path. --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 38f19e9a..9f11aca9 100755 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` CONFIG_BACKUP=false APT_HAS_UPDATED=false RESOURCES_TOP_DIR=$HOME/Pimoroni -VENV_BASH_SNIPPET=$RESOURCES_DIR/auto_venv.sh +VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh VENV_DIR=$HOME/.virtualenvs/pimoroni WD=`pwd` USAGE="./install.sh (--unstable)" @@ -76,7 +76,7 @@ find_config() { venv_bash_snippet() { if [ ! -f $VENV_BASH_SNIPPET ]; then cat << EOF > $VENV_BASH_SNIPPET -# Add `source $RESOURCES_DIR/auto_venv.sh` to your ~/.bashrc to activate +# Add `source $VENV_BASH_SNIPPET` to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! VENV_DIR="$VENV_DIR" if [ ! -f \$VENV_DIR/bin/activate ]; then From f1508a4939279be7c74efc666b32970b76a8f4f8 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 22 Nov 2023 10:42:39 +0000 Subject: [PATCH 47/69] Fix auto_venv.sh creation. --- install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 9f11aca9..6c9b4742 100755 --- a/install.sh +++ b/install.sh @@ -74,9 +74,11 @@ find_config() { } venv_bash_snippet() { + inform "Checking for $VENV_BASH_SNIPPET\n" if [ ! -f $VENV_BASH_SNIPPET ]; then + inform "Creating $VENV_BASH_SNIPPET\n" cat << EOF > $VENV_BASH_SNIPPET -# Add `source $VENV_BASH_SNIPPET` to your ~/.bashrc to activate +# Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! VENV_DIR="$VENV_DIR" if [ ! -f \$VENV_DIR/bin/activate ]; then From a1f67c9f9f0856626b25869a19508fef64753451 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 22 Nov 2023 17:15:30 +0000 Subject: [PATCH 48/69] examples/weather-and-light.py: fix bug with vertical label spacing. --- examples/weather-and-light.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/weather-and-light.py b/examples/weather-and-light.py index efbd2a88..fcfb5338 100755 --- a/examples/weather-and-light.py +++ b/examples/weather-and-light.py @@ -162,11 +162,6 @@ def draw_background(progress, period, day): return composite -def text_width(font, text): - x1, y1, x2, y2 = font.getbbox(text) - return x2 - x1 - - def text_size(font, text): x1, y1, x2, y2 = font.getbbox(text) return x2 - x1, y2 - y1 @@ -384,7 +379,8 @@ def describe_light(light): temp_string = f"{corr_temperature:.0f}°C" img = overlay_text(img, (68, 18), temp_string, font_lg, align_right=True) - spacing = text_width(font_lg, temp_string) + 1 + _, text_height = text_size(font_lg, temp_string) + spacing = text_height + 1 if min_temp is not None and max_temp is not None: range_string = f"{min_temp:.0f}-{max_temp:.0f}" else: @@ -398,7 +394,8 @@ def describe_light(light): corr_humidity = correct_humidity(humidity, temperature, corr_temperature) humidity_string = f"{corr_humidity:.0f}%" img = overlay_text(img, (68, 48), humidity_string, font_lg, align_right=True) - spacing = text_width(font_lg, humidity_string) + 1 + _, text_height = text_size(font_lg, humidity_string) + spacing = text_height + 1 humidity_desc = describe_humidity(corr_humidity).upper() img = overlay_text(img, (68, 48 + spacing), humidity_desc, font_sm, align_right=True, rectangle=True) humidity_icon = Image.open(f"{path}/icons/humidity-{humidity_desc.lower()}.png") @@ -408,7 +405,8 @@ def describe_light(light): light = ltr559.get_lux() light_string = f"{int(light):,}" img = overlay_text(img, (WIDTH - margin, 18), light_string, font_lg, align_right=True) - spacing = text_width(font_lg, light_string.replace(",", "")) + 1 + _, text_height = text_size(font_lg, light_string.replace(",", "")) + spacing = text_height + 1 light_desc = describe_light(light).upper() img = overlay_text(img, (WIDTH - margin - 1, 18 + spacing), light_desc, font_sm, align_right=True, rectangle=True) light_icon = Image.open(f"{path}/icons/bulb-{light_desc.lower()}.png") @@ -421,7 +419,8 @@ def describe_light(light): pressure_string = f"{int(mean_pressure):,} {trend}" img = overlay_text(img, (WIDTH - margin, 48), pressure_string, font_lg, align_right=True) pressure_desc = describe_pressure(mean_pressure).upper() - spacing = text_width(font_lg, pressure_string.replace(",", "")) + 1 + _, text_height = text_size(font_lg, pressure_string.replace(",", "")) + spacing = text_height + 1 img = overlay_text(img, (WIDTH - margin - 1, 48 + spacing), pressure_desc, font_sm, align_right=True, rectangle=True) pressure_icon = Image.open(f"{path}/icons/weather-{pressure_desc.lower()}.png") img.paste(pressure_icon, (80, 48), mask=pressure_icon) From e6cd19c57b210000b3fb01e6e4b133a57d16c042 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 23 Nov 2023 10:18:07 +0000 Subject: [PATCH 49/69] install.sh: rework for better error reporting and fix some bugs. Fix a bug where auto_venv.sh was being created in a non-existent directory. Trap exit codes for some commands and add some help text + GitHUb url at the end of the install process. Try to comment what some sections do, and insert linebreaks so they are more logically broken up in the installer output. Try to be more consistent with colours. Try to be more friendly with colours- remove full red warning text in favour of a prefix so the errors/warnings are easier to read. Return a failure exit code if bits of the script have failed. Try to re-order output so it's more logical. Re-word venv creation message. --- install.sh | 144 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 42 deletions(-) diff --git a/install.sh b/install.sh index 6c9b4742..d42c48d0 100755 --- a/install.sh +++ b/install.sh @@ -5,21 +5,21 @@ CONFIG_DIR="/boot/firmware" DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` CONFIG_BACKUP=false APT_HAS_UPDATED=false -RESOURCES_TOP_DIR=$HOME/Pimoroni -VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh -VENV_DIR=$HOME/.virtualenvs/pimoroni +RESOURCES_TOP_DIR="$HOME/Pimoroni" +VENV_BASH_SNIPPET="$RESOURCES_TOP_DIR/auto_venv.sh" +VENV_DIR="$HOME/.virtualenvs/pimoroni" WD=`pwd` USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() FORCE=false UNSTABLE=false PYTHON="python" +CMD_ERRORS=false user_check() { if [ $(id -u) -eq 0 ]; then - printf "Script should not be run as root. Try './install.sh'\n" - exit 1 + fatal "Script should not be run as root. Try './install.sh'\n" fi } @@ -54,29 +54,34 @@ inform() { } warning() { - echo -e "$(tput setaf 1)$1$(tput sgr0)" + echo -e "$(tput setaf 1)⚠ WARNING:$(tput sgr0) $1" +} + +fatal() { + echo -e "$(tput setaf 1)⚠ FATAL:$(tput sgr0) $1" + exit 1 } find_config() { if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then CONFIG_DIR="/boot" - if [ ! -f "$CONFIG_DIR/$CONFIG_FILE"]; then - warning "Could not find $CONFIG_FILE!" - exit 1 + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then + fatal "Could not find $CONFIG_FILE!" + fi + else + if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then + warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE" + warning "You might want to fix this!" fi - else - if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then - warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE" - warning "You might want to fix this!" - fi fi - inform "Using $CONFIG_FILE in $CONFIG_DIR" + inform "Using $CONFIG_FILE in $CONFIG_DIR" } venv_bash_snippet() { - inform "Checking for $VENV_BASH_SNIPPET\n" + inform "Checking for $VENV_BASH_SNIPPET\n" if [ ! -f $VENV_BASH_SNIPPET ]; then - inform "Creating $VENV_BASH_SNIPPET\n" + inform "Creating $VENV_BASH_SNIPPET\n" + mkdir -p $RESOURCES_TOP_DIR cat << EOF > $VENV_BASH_SNIPPET # Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! @@ -96,23 +101,32 @@ venv_check() { PYTHON_BIN=`which $PYTHON` if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then printf "This script should be run in a virtual Python environment.\n" - if confirm "Would you like us to create one for you?"; then + if confirm "Would you like us to create and/or use a default one?"; then + printf "\n" if [ ! -f $VENV_DIR/bin/activate ]; then - inform "Creating virtual Python environment in $VENV_DIR, please wait...\n" + inform "Creating a new virtual Python environment in $VENV_DIR, please wait...\n" mkdir -p $VENV_DIR /usr/bin/python3 -m venv $VENV_DIR --system-site-packages venv_bash_snippet + source $VENV_DIR/bin/activate else - inform "Found existing virtual Python environment in $VENV_DIR\n" + inform "Activating existing virtual Python environment in $VENV_DIR\n" + printf "source $VENV_DIR/bin/activate\n" + source $VENV_DIR/bin/activate fi - inform "Activating virtual Python environment in $VENV_DIR..." - inform "source $VENV_DIR/bin/activate\n" - source $VENV_DIR/bin/activate - else - exit 1 + printf "\n" + fatal "Please create and/or activate a virtual Python environment and try again!\n" fi fi + printf "\n" +} + +check_for_error() { + if [ $? -ne 0 ]; then + CMD_ERRORS=true + warning "^^^ 😬" + fi } function do_config_backup { @@ -132,6 +146,7 @@ function do_config_backup { function apt_pkg_install { PACKAGES=() PACKAGES_IN=("$@") + # Check the list of packages and only run update/install if we need to for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do PACKAGE="${PACKAGES_IN[$i]}" if [ "$PACKAGE" == "" ]; then continue; fi @@ -143,12 +158,14 @@ function apt_pkg_install { done PACKAGES="${PACKAGES[@]}" if ! [ "$PACKAGES" == "" ]; then - echo "Installing missing packages: $PACKAGES" + printf "\n" + inform "Installing missing packages: $PACKAGES" if [ ! $APT_HAS_UPDATED ]; then sudo apt update APT_HAS_UPDATED=true fi sudo apt install -y $PACKAGES + check_for_error if [ -f "$UNINSTALLER" ]; then echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER fi @@ -156,7 +173,9 @@ function apt_pkg_install { } function pip_pkg_install { + # A null Keyring prevents pip stalling in the background PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@" + check_for_error } while [[ $# -gt 0 ]]; do @@ -186,30 +205,33 @@ while [[ $# -gt 0 ]]; do esac done +printf "Installing $LIBRARY_NAME...\n\n" + user_check venv_check if [ ! -f `which $PYTHON` ]; then - printf "Python path $PYTHON not found!\n" - exit 1 + fatal "Python path $PYTHON not found!\n" fi PYTHON_VER=`$PYTHON --version` -printf "$LIBRARY_NAME Python Library: Installer\n\n" - inform "Checking Dependencies. Please wait..." +# Install toml and try to read pyproject.toml into bash variables + pip_pkg_install toml CONFIG_VARS=`$PYTHON - < $UNINSTALLER printf "It's recommended you run these steps manually.\n" printf "If you want to run the full script, open it in\n" @@ -244,19 +270,23 @@ exit 1 source $VIRTUAL_ENV/bin/activate EOF -if $UNSTABLE; then - warning "Installing unstable library from source.\n\n" -else - printf "Installing stable library from pypi.\n\n" -fi +printf "\n" inform "Installing for $PYTHON_VER...\n" + +# Install apt packages from pyproject.toml / tool.pimoroni.apt_packages apt_pkg_install "${APT_PACKAGES[@]}" + +printf "\n" + if $UNSTABLE; then + warning "Installing unstable library from source.\n" pip_pkg_install . else + inform "Installing stable library from pypi.\n" pip_pkg_install $LIBRARY_NAME fi + if [ $? -eq 0 ]; then success "Done!\n" echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER @@ -266,6 +296,8 @@ cd $WD find_config +# Run the setup commands from pyproject.toml / tool.pimoroni.commands + for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do CMD="${SETUP_CMDS[$i]}" # Attempt to catch anything that touches config.txt and trigger a backup @@ -273,13 +305,18 @@ for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do do_config_backup fi eval $CMD + check_for_error done +printf "\n" + +# Add the config.txt entries from pyproject.toml / tool.pimoroni.configtxt + for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do CONFIG_LINE="${CONFIG_TXT[$i]}" if ! [ "$CONFIG_LINE" == "" ]; then do_config_backup - inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE\n" + inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE" sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE @@ -287,6 +324,10 @@ for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do fi done +printf "\n" + +# Just a straight copy of the examples/ dir into ~/Pimoroni/board/examples + if [ -d "examples" ]; then if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then inform "Copying examples to $RESOURCES_DIR" @@ -298,9 +339,12 @@ fi printf "\n" +# Use pdoc to generate basic documentation from the installed module + if confirm "Would you like to generate documentation?"; then + inform "Installing pdoc. Please wait..." pip_pkg_install pdoc - printf "Generating documentation.\n" + inform "Generating documentation.\n" $PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null if [ $? -eq 0 ]; then inform "Documentation saved to $RESOURCES_DIR/docs" @@ -310,6 +354,22 @@ if confirm "Would you like to generate documentation?"; then fi fi -success "\nAll done!" -inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" -inform "Find uninstall steps in $UNINSTALLER\n" +printf "\n" + +if [ "$CMD_ERRORS" = true ]; then + warning "One or more setup commands appear to have failed." + printf "This might prevent things from working properly.\n" + printf "Make sure your OS is up to date and try re-running this installer.\n" + printf "If things still don't work, report this or find help at $GITHUB_URL.\n\n" +else + success "\nAll done!" +fi + +printf "If this is your first time installing you should reboot for hardware changes to take effect.\n" +printf "Find uninstall steps in $UNINSTALLER\n\n" + +if [ "$CMD_ERRORS" = true ]; then + exit 1 +else + exit 0 +fi \ No newline at end of file From 8b1ab0afc6caa393004d494c927452b933752011 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 10 Jan 2024 14:29:50 +0000 Subject: [PATCH 50/69] Sync with latest boilerplate. * CI: Update GitHub Actions versions. * QA: Add shellcheck and fix/ignore all issues. * install.sh: slightly better feedback for setup commands. --- .github/workflows/build.yml | 6 +- .github/workflows/qa.yml | 9 ++- .github/workflows/test.yml | 2 +- Makefile | 5 +- check.sh | 20 +++--- install.sh | 119 ++++++++++++++++++------------------ uninstall.sh | 14 ++--- 7 files changed, 87 insertions(+), 88 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87200efb..07620e34 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,10 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} @@ -35,7 +35,7 @@ jobs: make build - name: Upload Packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.RELEASE_FILE }} path: dist/ diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 4f858832..ac672a52 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -10,16 +10,15 @@ jobs: test: name: linting & spelling runs-on: ubuntu-latest - env: TERM: xterm-256color steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python '3,11' - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -34,3 +33,7 @@ jobs: - name: Run Code Checks run: | make check + + - name: Run Bash Code Checks + run: | + make shellcheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 016a6780..6f8cff73 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} diff --git a/Makefile b/Makefile index 9e0c15c5..34f4a7dd 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,14 @@ uninstall: dev-deps: python3 -m pip install -r requirements-dev.txt - sudo apt install dos2unix + sudo apt install dos2unix shellcheck check: @bash check.sh +shellcheck: + shellcheck *.sh + qa: tox -e qa diff --git a/check.sh b/check.sh index 4395d89c..38dfc3a1 100755 --- a/check.sh +++ b/check.sh @@ -3,9 +3,9 @@ # This script handles some basic QA checks on the source NOPOST=$1 -LIBRARY_NAME=`hatch project metadata name` -LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'` -POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'` +LIBRARY_NAME=$(hatch project metadata name) +LIBRARY_VERSION=$(hatch version | awk -F "." '{print $1"."$2"."$3}') +POST_VERSION=$(hatch version | awk -F "." '{print substr($4,0,length($4))}') TERM=${TERM:="xterm-256color"} success() { @@ -29,7 +29,7 @@ while [[ $# -gt 0 ]]; do ;; *) if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; + printf "Unrecognised option: %s\n" "$1"; exit 1 fi POSITIONAL_ARGS+=("$1") @@ -40,8 +40,7 @@ done inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n" inform "Checking for trailing whitespace..." -grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO -if [[ $? -eq 0 ]]; then +if grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO; then warning "Trailing whitespace found!" exit 1 else @@ -50,8 +49,7 @@ fi printf "\n" inform "Checking for DOS line-endings..." -grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile -if [[ $? -eq 0 ]]; then +if grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile; then warning "DOS line-endings found!" exit 1 else @@ -60,8 +58,7 @@ fi printf "\n" inform "Checking CHANGELOG.md..." -cat CHANGELOG.md | grep ^${LIBRARY_VERSION} > /dev/null 2>&1 -if [[ $? -eq 1 ]]; then +if ! grep "^${LIBRARY_VERSION}" CHANGELOG.md > /dev/null 2>&1; then warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md." exit 1 else @@ -70,8 +67,7 @@ fi printf "\n" inform "Checking for git tag ${LIBRARY_VERSION}..." -git tag -l | grep -E "${LIBRARY_VERSION}$" -if [[ $? -eq 1 ]]; then +if ! git tag -l | grep -E "${LIBRARY_VERSION}$"; then warning "Missing git tag for version ${LIBRARY_VERSION}" fi printf "\n" diff --git a/install.sh b/install.sh index d42c48d0..059d3c46 100755 --- a/install.sh +++ b/install.sh @@ -1,14 +1,13 @@ #!/bin/bash -LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}') CONFIG_FILE=config.txt CONFIG_DIR="/boot/firmware" -DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` +DATESTAMP=$(date "+%Y-%m-%d-%H-%M-%S") CONFIG_BACKUP=false APT_HAS_UPDATED=false RESOURCES_TOP_DIR="$HOME/Pimoroni" VENV_BASH_SNIPPET="$RESOURCES_TOP_DIR/auto_venv.sh" VENV_DIR="$HOME/.virtualenvs/pimoroni" -WD=`pwd` USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() FORCE=false @@ -18,7 +17,7 @@ CMD_ERRORS=false user_check() { - if [ $(id -u) -eq 0 ]; then + if [ "$(id -u)" -eq 0 ]; then fatal "Script should not be run as root. Try './install.sh'\n" fi } @@ -36,15 +35,6 @@ confirm() { fi } -prompt() { - read -r -p "$1 [y/N] " response < /dev/tty - if [[ $response =~ ^(yes|y|Y)$ ]]; then - true - else - false - fi -} - success() { echo -e "$(tput setaf 2)$1$(tput sgr0)" } @@ -79,10 +69,10 @@ find_config() { venv_bash_snippet() { inform "Checking for $VENV_BASH_SNIPPET\n" - if [ ! -f $VENV_BASH_SNIPPET ]; then + if [ ! -f "$VENV_BASH_SNIPPET" ]; then inform "Creating $VENV_BASH_SNIPPET\n" - mkdir -p $RESOURCES_TOP_DIR - cat << EOF > $VENV_BASH_SNIPPET + mkdir -p "$RESOURCES_TOP_DIR" + cat << EOF > "$VENV_BASH_SNIPPET" # Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! VENV_DIR="$VENV_DIR" @@ -98,21 +88,23 @@ EOF } venv_check() { - PYTHON_BIN=`which $PYTHON` + PYTHON_BIN=$(which "$PYTHON") if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then printf "This script should be run in a virtual Python environment.\n" if confirm "Would you like us to create and/or use a default one?"; then printf "\n" - if [ ! -f $VENV_DIR/bin/activate ]; then + if [ ! -f "$VENV_DIR/bin/activate" ]; then inform "Creating a new virtual Python environment in $VENV_DIR, please wait...\n" - mkdir -p $VENV_DIR - /usr/bin/python3 -m venv $VENV_DIR --system-site-packages + mkdir -p "$VENV_DIR" + /usr/bin/python3 -m venv "$VENV_DIR" --system-site-packages venv_bash_snippet - source $VENV_DIR/bin/activate + # shellcheck disable=SC1091 + source "$VENV_DIR/bin/activate" else inform "Activating existing virtual Python environment in $VENV_DIR\n" - printf "source $VENV_DIR/bin/activate\n" - source $VENV_DIR/bin/activate + printf "source \"%s/bin/activate\"\n" "$VENV_DIR" + # shellcheck disable=SC1091 + source "$VENV_DIR/bin/activate" fi else printf "\n" @@ -125,7 +117,7 @@ venv_check() { check_for_error() { if [ $? -ne 0 ]; then CMD_ERRORS=true - warning "^^^ 😬" + warning "^^^ 😬 previous command did not exit cleanly!" fi } @@ -134,29 +126,29 @@ function do_config_backup { CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n" - sudo cp $CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME - mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG_DIR/$CONFIG_FILE $RESOURCES_TOP_DIR/config-backups/$FILENAME + sudo cp "$CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME" + mkdir -p "$RESOURCES_TOP_DIR/config-backups/" + cp $CONFIG_DIR/$CONFIG_FILE "$RESOURCES_TOP_DIR/config-backups/$FILENAME" if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> $UNINSTALLER + echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> "$UNINSTALLER" fi fi } function apt_pkg_install { - PACKAGES=() + PACKAGES_NEEDED=() PACKAGES_IN=("$@") # Check the list of packages and only run update/install if we need to for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do PACKAGE="${PACKAGES_IN[$i]}" if [ "$PACKAGE" == "" ]; then continue; fi - printf "Checking for $PACKAGE\n" - dpkg -L $PACKAGE > /dev/null 2>&1 + printf "Checking for %s\n" "$PACKAGE" + dpkg -L "$PACKAGE" > /dev/null 2>&1 if [ "$?" == "1" ]; then - PACKAGES+=("$PACKAGE") + PACKAGES_NEEDED+=("$PACKAGE") fi done - PACKAGES="${PACKAGES[@]}" + PACKAGES="${PACKAGES_NEEDED[*]}" if ! [ "$PACKAGES" == "" ]; then printf "\n" inform "Installing missing packages: $PACKAGES" @@ -164,10 +156,10 @@ function apt_pkg_install { sudo apt update APT_HAS_UPDATED=true fi - sudo apt install -y $PACKAGES + sudo apt install -y "$PACKAGES" check_for_error if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER + echo "apt uninstall -y $PACKAGES" >> "$UNINSTALLER" fi fi } @@ -196,8 +188,8 @@ while [[ $# -gt 0 ]]; do ;; *) if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; - printf "Usage: $USAGE\n"; + printf "Unrecognised option: %s\n" "$1"; + printf "Usage: %s\n" "$USAGE"; exit 1 fi POSITIONAL_ARGS+=("$1") @@ -205,16 +197,16 @@ while [[ $# -gt 0 ]]; do esac done -printf "Installing $LIBRARY_NAME...\n\n" +printf "Installing %s...\n\n" "$LIBRARY_NAME" user_check venv_check -if [ ! -f `which $PYTHON` ]; then - fatal "Python path $PYTHON not found!\n" +if [ ! -f "$(which "$PYTHON")" ]; then + fatal "Python path %s not found!\n" "$PYTHON" fi -PYTHON_VER=`$PYTHON --version` +PYTHON_VER=$($PYTHON --version) inform "Checking Dependencies. Please wait..." @@ -222,7 +214,8 @@ inform "Checking Dependencies. Please wait..." pip_pkg_install toml -CONFIG_VARS=`$PYTHON - < $UNINSTALLER +cat << EOF > "$UNINSTALLER" printf "It's recommended you run these steps manually.\n" printf "If you want to run the full script, open it in\n" printf "an editor and remove 'exit 1' from below.\n" @@ -284,27 +279,30 @@ if $UNSTABLE; then pip_pkg_install . else inform "Installing stable library from pypi.\n" - pip_pkg_install $LIBRARY_NAME + pip_pkg_install "$LIBRARY_NAME" fi +# shellcheck disable=SC2181 # One of two commands run, depending on --unstable flag if [ $? -eq 0 ]; then success "Done!\n" - echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER + echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> "$UNINSTALLER" fi -cd $WD - find_config +printf "\n" + # Run the setup commands from pyproject.toml / tool.pimoroni.commands +inform "Running setup commands...\n" for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do CMD="${SETUP_CMDS[$i]}" # Attempt to catch anything that touches config.txt and trigger a backup if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then do_config_backup fi - eval $CMD + printf "\"%s\"\n" "$CMD" + eval "$CMD" check_for_error done @@ -319,7 +317,7 @@ for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE" sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then - printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE + printf "%s \n" "$CONFIG_LINE" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE fi fi done @@ -331,8 +329,8 @@ printf "\n" if [ -d "examples" ]; then if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then inform "Copying examples to $RESOURCES_DIR" - cp -r examples/ $RESOURCES_DIR - echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER + cp -r examples/ "$RESOURCES_DIR" + echo "rm -r $RESOURCES_DIR" >> "$UNINSTALLER" success "Done!" fi fi @@ -345,8 +343,7 @@ if confirm "Would you like to generate documentation?"; then inform "Installing pdoc. Please wait..." pip_pkg_install pdoc inform "Generating documentation.\n" - $PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null - if [ $? -eq 0 ]; then + if $PYTHON -m pdoc "$LIBRARY_NAME" -o "$RESOURCES_DIR/docs" > /dev/null; then inform "Documentation saved to $RESOURCES_DIR/docs" success "Done!" else @@ -360,16 +357,16 @@ if [ "$CMD_ERRORS" = true ]; then warning "One or more setup commands appear to have failed." printf "This might prevent things from working properly.\n" printf "Make sure your OS is up to date and try re-running this installer.\n" - printf "If things still don't work, report this or find help at $GITHUB_URL.\n\n" + printf "If things still don't work, report this or find help at %s.\n\n" "$GITHUB_URL" else success "\nAll done!" fi printf "If this is your first time installing you should reboot for hardware changes to take effect.\n" -printf "Find uninstall steps in $UNINSTALLER\n\n" +printf "Find uninstall steps in %s\n\n" "$UNINSTALLER" if [ "$CMD_ERRORS" = true ]; then exit 1 else exit 0 -fi \ No newline at end of file +fi diff --git a/uninstall.sh b/uninstall.sh index f213fc52..3314b7fc 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,13 +1,13 @@ #!/bin/bash FORCE=false -LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}') RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME PYTHON="python" venv_check() { - PYTHON_BIN=`which $PYTHON` + PYTHON_BIN=$(which $PYTHON) if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then printf "This script should be run in a virtual Python environment.\n" exit 1 @@ -15,7 +15,7 @@ venv_check() { } user_check() { - if [ $(id -u) -eq 0 ]; then + if [ "$(id -u)" -eq 0 ]; then printf "Script should not be run as root. Try './uninstall.sh'\n" exit 1 fi @@ -55,17 +55,17 @@ warning() { echo -e "$(tput setaf 1)$1$(tput sgr0)" } -printf "$LIBRARY_NAME Python Library: Uninstaller\n\n" +printf "%s Python Library: Uninstaller\n\n" "$LIBRARY_NAME" user_check venv_check printf "Uninstalling for Python 3...\n" -$PYTHON -m pip uninstall $LIBRARY_NAME +$PYTHON -m pip uninstall "$LIBRARY_NAME" -if [ -d $RESOURCES_DIR ]; then +if [ -d "$RESOURCES_DIR" ]; then if confirm "Would you like to delete $RESOURCES_DIR?"; then - rm -r $RESOURCES_DIR + rm -r "$RESOURCES_DIR" fi fi From 97c824a23e9dd1245b7a4f94f5e1a473129dba77 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 10 Jan 2024 14:52:23 +0000 Subject: [PATCH 51/69] Sync with latest boilerplate. * install.sh: fix quoting bug in do_config_backup. * install.sh: don't output printf commands. --- install.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 059d3c46..bb671f29 100755 --- a/install.sh +++ b/install.sh @@ -126,7 +126,7 @@ function do_config_backup { CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n" - sudo cp "$CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME" + sudo cp "$CONFIG_DIR/$CONFIG_FILE" "$CONFIG_DIR/$FILENAME" mkdir -p "$RESOURCES_TOP_DIR/config-backups/" cp $CONFIG_DIR/$CONFIG_FILE "$RESOURCES_TOP_DIR/config-backups/$FILENAME" if [ -f "$UNINSTALLER" ]; then @@ -301,7 +301,9 @@ for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then do_config_backup fi - printf "\"%s\"\n" "$CMD" + if [[ ! "$CMD" == printf* ]]; then + printf "Running: \"%s\"\n" "$CMD" + fi eval "$CMD" check_for_error done From 34c3efaa0e538e2e9d753474a0dfd28b94ef1df2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 17 Apr 2024 09:21:59 +0100 Subject: [PATCH 52/69] Standardise on GPIOn pin labels. As of https://github.com/raspberrypi/linux/commit/bd9542b8271ccb251490c814e9237ed94d7ceb56 all downstream GPIO line names use the form GPIOn, and PIN_n is deprecated. Simplify code and examples to reflect this. --- enviroplus/gas.py | 8 ++------ examples/all-in-one-enviro-mini.py | 4 ++-- examples/all-in-one-no-pm.py | 4 ++-- examples/all-in-one.py | 4 ++-- examples/combined.py | 4 ++-- examples/lcd.py | 4 ++-- examples/mqtt-all.py | 4 ++-- examples/noise-amps-at-freqs.py | 4 ++-- examples/noise-profile.py | 4 ++-- examples/sensorcommunity.py | 4 ++-- examples/sensorcommunity_combined.py | 4 ++-- examples/weather-and-light.py | 4 ++-- tests/conftest.py | 1 + 13 files changed, 25 insertions(+), 28 deletions(-) diff --git a/enviroplus/gas.py b/enviroplus/gas.py index e1c4b76d..3583428a 100644 --- a/enviroplus/gas.py +++ b/enviroplus/gas.py @@ -11,11 +11,7 @@ MICS6814_GAIN = 6.144 OUTH = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.ACTIVE) -PLATFORMS = { - "Radxa ROCK 5B": {"heater": ("PIN_18", OUTH)}, - "Raspberry Pi 5": {"heater": ("PIN18", OUTH)}, - "Raspberry Pi 4": {"heater": ("GPIO24", OUTH)} -} + ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE _is_setup = False @@ -68,7 +64,7 @@ def setup(): else: adc.set_sample_rate(1600) - _heater = gpiodevice.get_pins_for_platform(PLATFORMS)[0] + _heater = gpiodevice.get_pin("GPIO24") atexit.register(cleanup) diff --git a/examples/all-in-one-enviro-mini.py b/examples/all-in-one-enviro-mini.py index 01491f37..4aea5c4d 100755 --- a/examples/all-in-one-enviro-mini.py +++ b/examples/all-in-one-enviro-mini.py @@ -37,8 +37,8 @@ st7735 = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/all-in-one-no-pm.py b/examples/all-in-one-no-pm.py index c2d9078d..c0172054 100755 --- a/examples/all-in-one-no-pm.py +++ b/examples/all-in-one-no-pm.py @@ -38,8 +38,8 @@ st7735 = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/all-in-one.py b/examples/all-in-one.py index 9b7121ea..b558f486 100755 --- a/examples/all-in-one.py +++ b/examples/all-in-one.py @@ -44,8 +44,8 @@ st7735 = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/combined.py b/examples/combined.py index eaab10f3..79e5d621 100755 --- a/examples/combined.py +++ b/examples/combined.py @@ -47,8 +47,8 @@ st7735 = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/lcd.py b/examples/lcd.py index cb2af2f3..97c2be44 100755 --- a/examples/lcd.py +++ b/examples/lcd.py @@ -21,8 +21,8 @@ disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index 7238d3b0..ecab4a35 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -234,8 +234,8 @@ def main(): disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/noise-amps-at-freqs.py b/examples/noise-amps-at-freqs.py index 825f53cf..957511b4 100755 --- a/examples/noise-amps-at-freqs.py +++ b/examples/noise-amps-at-freqs.py @@ -18,8 +18,8 @@ disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/noise-profile.py b/examples/noise-profile.py index 17ada829..be6a1855 100755 --- a/examples/noise-profile.py +++ b/examples/noise-profile.py @@ -16,8 +16,8 @@ disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/sensorcommunity.py b/examples/sensorcommunity.py index 7128ad72..cdd3a480 100755 --- a/examples/sensorcommunity.py +++ b/examples/sensorcommunity.py @@ -40,8 +40,8 @@ disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/sensorcommunity_combined.py b/examples/sensorcommunity_combined.py index 8dac7232..a7aeb7b8 100644 --- a/examples/sensorcommunity_combined.py +++ b/examples/sensorcommunity_combined.py @@ -153,8 +153,8 @@ def check_wifi(): st7735 = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/examples/weather-and-light.py b/examples/weather-and-light.py index fcfb5338..04f1bd8f 100755 --- a/examples/weather-and-light.py +++ b/examples/weather-and-light.py @@ -291,8 +291,8 @@ def describe_light(light): disp = st7735.ST7735( port=0, cs=1, - dc="PIN21", # "GPIO9" on a Raspberry Pi 4 - backlight="PIN32", # "GPIO12" on a Raspberry Pi 4 + dc="GPIO9", + backlight="GPIO12", rotation=270, spi_speed_hz=10000000 ) diff --git a/tests/conftest.py b/tests/conftest.py index 6ad5557c..20237430 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,6 +45,7 @@ def gpiod(): def gpiodevice(): gpiodevice = mock.Mock() gpiodevice.get_pins_for_platform.return_value = [(mock.Mock(), 0)] + gpiodevice.get_pin.return_value = (mock.Mock(), 0) sys.modules["gpiodevice"] = gpiodevice yield gpiodevice From b7fad2415edcd667b6efb190298334a4b8347251 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 17 Apr 2024 10:01:25 +0100 Subject: [PATCH 53/69] Prep for v1.0.0. --- CHANGELOG.md | 5 +++++ enviroplus/__init__.py | 2 +- pyproject.toml | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f175aa..67206be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +1.0.0 +----- + +* BREAKING: Port to gpiod/gpiodevice for Pi 5/Bookworm. + 0.0.6 ----- diff --git a/enviroplus/__init__.py b/enviroplus/__init__.py index 034f46c3..5becc17c 100644 --- a/enviroplus/__init__.py +++ b/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = "0.0.6" +__version__ = "1.0.0" diff --git a/pyproject.toml b/pyproject.toml index a6a4e6b1..d97b4171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,8 @@ classifiers = [ "Topic :: System :: Hardware", ] dependencies = [ + "gpiod >= 2.1.3", + "gpiodevice >= 0.0.3", "pimoroni-bme280 >= 1.0.0", "pms5003 >= 1.0.0", "ltr559 >= 1.0.0", From 6391486dcadb32c0bdf4f7d9547bea32219cd20a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 17 Apr 2024 09:37:23 +0100 Subject: [PATCH 54/69] install.sh: drop quotes around apt packages. Quotes would cause a list of packages to be treated as a single package and lookup would fail. Reported-by: thirdr --- install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index bb671f29..495919b4 100755 --- a/install.sh +++ b/install.sh @@ -156,7 +156,8 @@ function apt_pkg_install { sudo apt update APT_HAS_UPDATED=true fi - sudo apt install -y "$PACKAGES" + # shellcheck disable=SC2086 + sudo apt install -y $PACKAGES check_for_error if [ -f "$UNINSTALLER" ]; then echo "apt uninstall -y $PACKAGES" >> "$UNINSTALLER" From 65c7e9da80521ef4d42a9cc7ba0cd99f7a086635 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 17 Apr 2024 11:16:46 +0100 Subject: [PATCH 55/69] README.md: Absolute image URLs for PyPI. --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- enviroplus/__init__.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67206be0..d28efc61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +1.0.1 +----------- + +* README.md: Fix images + 1.0.0 ----- diff --git a/README.md b/README.md index 72e0597a..45436ff4 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ You are best using the "One-line" install method if you want all of the UART ser **Note** The code in this repository supports both the Enviro+ and Enviro Mini boards. _The Enviro Mini board does not have the Gas sensor or the breakout for the PM sensor._ -![Enviro Plus pHAT](./Enviro-Plus-pHAT.jpg) -![Enviro Mini pHAT](./Enviro-mini-pHAT.jpg) +![Enviro Plus pHAT](https://raw.githubusercontent.com/pimoroni/enviroplus-python/main/Enviro-Plus-pHAT.jpg) +![Enviro Mini pHAT](https://raw.githubusercontent.com/pimoroni/enviroplus-python/main/Enviro-mini-pHAT.jpg) :warning: This library now supports Python 3 only, Python 2 is EOL - https://www.python.org/doc/sunset-python-2/ diff --git a/enviroplus/__init__.py b/enviroplus/__init__.py index 5becc17c..5c4105cd 100644 --- a/enviroplus/__init__.py +++ b/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = "1.0.0" +__version__ = "1.0.1" From 288b64ca7c87a1127f5a757405a56497fe164f96 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 22 Apr 2024 11:31:20 +0100 Subject: [PATCH 56/69] Install: Enable serial. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d97b4171..25179f4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,5 +141,5 @@ commands = [ "sudo raspi-config nonint do_i2c 0", "printf \"Setting up serial for PMS5003..\\n\"", "sudo raspi-config nonint do_serial_cons 1", - "sudo raspi-config nonint do_serial_hw 1" + "sudo raspi-config nonint do_serial_hw 0" ] From 7ea2264a188042a180094ba14279bfd47315eb7a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 22 Apr 2024 11:31:31 +0100 Subject: [PATCH 57/69] README.md: Update install instructions. --- README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 45436ff4..83098815 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Designed for environmental monitoring, Enviro+ lets you measure air quality (pollutant gases and particulates), temperature, pressure, humidity, light, and noise level. Learn more - https://shop.pimoroni.com/products/enviro-plus -[![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/enviroplus-python) -[![Coverage Status](https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/enviroplus-python?branch=master) +[![Build Status](https://img.shields.io/github/actions/workflow/status/pimoroni/enviroplus-python/test.yml?branch=main)](https://github.com/pimoroni/enviroplus-python/actions/workflows/test.yml) +[![Coverage Status](https://coveralls.io/repos/github/pimoroni/enviroplus-python/badge.svg?branch=main)](https://coveralls.io/github/pimoroni/enviroplus-python?branch=main) [![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) [![Python Versions](https://img.shields.io/pypi/pyversions/enviroplus.svg)](https://pypi.python.org/pypi/enviroplus) @@ -18,25 +18,24 @@ You are best using the "One-line" install method if you want all of the UART ser :warning: This library now supports Python 3 only, Python 2 is EOL - https://www.python.org/doc/sunset-python-2/ -## One-line (Installs from GitHub) - -```bash -curl -sSL https://get.pimoroni.com/enviroplus | bash -``` - -**Note** report issues with one-line installer here: https://github.com/pimoroni/get - -## Or... Install and configure dependencies from GitHub: +## Install and configure dependencies from GitHub: * `git clone https://github.com/pimoroni/enviroplus-python` * `cd enviroplus-python` -* `sudo ./install.sh` +* `./install.sh` **Note** Raspbian/Raspberry Pi OS Lite users may first need to install git: `sudo apt install git` ## Or... Install from PyPi and configure manually: -* Run `sudo python3 -m pip install enviroplus` +* `python3 -m venv --system-site-packages $HOME/.virtualenvs/pimoroni` +* Run `python3 -m pip install enviroplus` + +And install additional dependencies: + +```bash +sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools +``` **Note** this will not perform any of the required configuration changes on your Pi, you may additionally need to: @@ -45,15 +44,17 @@ curl -sSL https://get.pimoroni.com/enviroplus | bash And if you're using a PMS5003 sensor you will need to: -* Enable serial: `raspi-config nonint set_config_var enable_uart 1 /boot/config.txt` -* Disable serial terminal: `sudo raspi-config nonint do_serial 1` +### Bookworm + +* Enable serial: `raspi-config nonint do_serial_hw 0` +* Disable serial terminal: `raspi-config nonint do_serial_cons 1` * Add `dtoverlay=pi3-miniuart-bt` to your `/boot/config.txt` -And install additional dependencies: +### Bullseye -```bash -sudo apt install python3-numpy python3-smbus python3-pil python3-setuptools -``` +* Enable serial: `raspi-config nonint set_config_var enable_uart 1 /boot/config.txt` +* Disable serial terminal: `sudo raspi-config nonint do_serial 1` +* Add `dtoverlay=pi3-miniuart-bt` to your `/boot/config.txt` ## Alternate Software & User Projects From 345d75da1be9ed450d2d390b7a9d93125aca4114 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 23 Apr 2024 10:24:39 +0100 Subject: [PATCH 58/69] Gas: Fix gpiodevice.get_pin. --- enviroplus/gas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enviroplus/gas.py b/enviroplus/gas.py index 3583428a..1e78010f 100644 --- a/enviroplus/gas.py +++ b/enviroplus/gas.py @@ -64,7 +64,7 @@ def setup(): else: adc.set_sample_rate(1600) - _heater = gpiodevice.get_pin("GPIO24") + _heater = gpiodevice.get_pin("GPIO24", "EnviroPlus", OUTH) atexit.register(cleanup) From b2a076a05d551fa7ea5e261224ea6da7f5ed85f3 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 23 Apr 2024 10:27:30 +0100 Subject: [PATCH 59/69] README.md: Add note about enabling venv. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 83098815..c3e492de 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ You are best using the "One-line" install method if you want all of the UART ser * `cd enviroplus-python` * `./install.sh` +**Note** Libraries will be installed in the "pimoroni" virtual environment, you will need to activate it to run examples: + +``` +source ~/.virtualenvs/pimoroni/bin/activate +``` + **Note** Raspbian/Raspberry Pi OS Lite users may first need to install git: `sudo apt install git` ## Or... Install from PyPi and configure manually: From cbfd4418836e03fce4a8711cd909863414340de0 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 23 Apr 2024 11:18:33 +0100 Subject: [PATCH 60/69] Prep for v1.0.2. --- CHANGELOG.md | 9 ++++++++- enviroplus/__init__.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28efc61..47cb04d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ +1.0.2 +----- + +* README.md: Update install instructions +* Fix installer to enable serial +* Fix gas sensor heater pin + 1.0.1 ------------ +----- * README.md: Fix images diff --git a/enviroplus/__init__.py b/enviroplus/__init__.py index 5c4105cd..7863915f 100644 --- a/enviroplus/__init__.py +++ b/enviroplus/__init__.py @@ -1 +1 @@ -__version__ = "1.0.1" +__version__ = "1.0.2" From 7972ca33859bd516a49393be2b50ae36505b2ac3 Mon Sep 17 00:00:00 2001 From: Hel Gibbons <50950368+helgibbons@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:35:11 +0100 Subject: [PATCH 61/69] Update README.md Remove outdated mention of one line installer --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c3e492de..d76de497 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ Designed for environmental monitoring, Enviro+ lets you measure air quality (pol # Installing -You are best using the "One-line" install method if you want all of the UART serial configuration for the PMS5003 particulate matter sensor to run automatically. - **Note** The code in this repository supports both the Enviro+ and Enviro Mini boards. _The Enviro Mini board does not have the Gas sensor or the breakout for the PM sensor._ ![Enviro Plus pHAT](https://raw.githubusercontent.com/pimoroni/enviroplus-python/main/Enviro-Plus-pHAT.jpg) From d283e14dd7608f1f92a43456c21214887f97b53f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 24 Apr 2024 15:15:48 +0100 Subject: [PATCH 62/69] Bump PMS5003 to v1.0.1. Includes better support for Pi devices, defaults to Pi-compatible pins and includes support for custom GPIO pins. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 25179f4d..7c565bb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "gpiod >= 2.1.3", "gpiodevice >= 0.0.3", "pimoroni-bme280 >= 1.0.0", - "pms5003 >= 1.0.0", + "pms5003 >= 1.0.1", "ltr559 >= 1.0.0", "st7735 >= 1.0.0", "ads1015 >= 1.0.0", From 655f9f39b865b799e78afff5ebdbce172d1eaf0b Mon Sep 17 00:00:00 2001 From: idotj Date: Thu, 23 May 2024 10:49:34 +0200 Subject: [PATCH 63/69] Fix path for Bookworm Fix path for config.txt changes in Bookworm --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d76de497..b2033326 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ And if you're using a PMS5003 sensor you will need to: * Enable serial: `raspi-config nonint do_serial_hw 0` * Disable serial terminal: `raspi-config nonint do_serial_cons 1` -* Add `dtoverlay=pi3-miniuart-bt` to your `/boot/config.txt` +* Add `dtoverlay=pi3-miniuart-bt` to your `/boot/firmware/config.txt` ### Bullseye From 72267be957c4fe21cf46b420922bf06282dac9fe Mon Sep 17 00:00:00 2001 From: Alexandre Esse Date: Tue, 11 Jun 2024 01:21:41 +0200 Subject: [PATCH 64/69] examples: add shebang Signed-off-by: Alexandre Esse --- examples/noise-amps-at-freqs.py | 2 ++ examples/noise-profile.py | 2 ++ examples/sensorcommunity_combined.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/examples/noise-amps-at-freqs.py b/examples/noise-amps-at-freqs.py index 957511b4..75497df2 100755 --- a/examples/noise-amps-at-freqs.py +++ b/examples/noise-amps-at-freqs.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import st7735 from PIL import Image, ImageDraw diff --git a/examples/noise-profile.py b/examples/noise-profile.py index be6a1855..480815e6 100755 --- a/examples/noise-profile.py +++ b/examples/noise-profile.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import st7735 from PIL import Image, ImageDraw diff --git a/examples/sensorcommunity_combined.py b/examples/sensorcommunity_combined.py index a7aeb7b8..bb9869d6 100644 --- a/examples/sensorcommunity_combined.py +++ b/examples/sensorcommunity_combined.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import colorsys import logging import time From b6fd390e03843d16d20970a331635c382f1176e5 Mon Sep 17 00:00:00 2001 From: Alexandre Esse Date: Tue, 11 Jun 2024 03:15:48 +0200 Subject: [PATCH 65/69] mqtt-all: fix display box The previous display box function wasn't properly used. This fix the information displayed on the enviro+ display. Signed-off-by: Alexandre Esse --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index ecab4a35..db4cf875 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -136,7 +136,7 @@ def display_status(disp, mqtt_broker): message = f"{device_serial_number}\nWi-Fi: {wifi_status}\nmqtt-broker: {mqtt_broker}" img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) - x1, y1, x2, y2 = font.getbbox(message) + x1, y1, x2, y2 = draw.textbbox((0,0), message, font=font) size_x = x2 - x1 size_y = y2 - y1 x = (WIDTH - size_x) / 2 From f32e560580d32356858fddae2a3e18ef2eabb0ae Mon Sep 17 00:00:00 2001 From: Alexandre Esse Date: Tue, 11 Jun 2024 03:22:26 +0200 Subject: [PATCH 66/69] mqtt-all: update display message Signed-off-by: Alexandre Esse --- examples/mqtt-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt-all.py b/examples/mqtt-all.py index db4cf875..b4fd19ef 100755 --- a/examples/mqtt-all.py +++ b/examples/mqtt-all.py @@ -133,7 +133,7 @@ def display_status(disp, mqtt_broker): text_colour = (255, 255, 255) back_colour = (0, 170, 170) if check_wifi() else (85, 15, 15) device_serial_number = get_serial_number() - message = f"{device_serial_number}\nWi-Fi: {wifi_status}\nmqtt-broker: {mqtt_broker}" + message = f"Serial: {device_serial_number}\nWi-Fi: {wifi_status}\nmqtt-broker: {mqtt_broker}" img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) draw = ImageDraw.Draw(img) x1, y1, x2, y2 = draw.textbbox((0,0), message, font=font) From f2b11de49d0499d3b3d430208d61aba4f2d59a7f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 18 Mar 2025 16:31:53 +0000 Subject: [PATCH 67/69] packaging: sync with boilerplate, move example requirements. --- Makefile | 5 ++++- install.sh | 20 +++++++++++++++----- pyproject.toml | 8 +------- requirements-examples.txt | 7 +++++++ tox.ini | 11 ++--------- 5 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 requirements-examples.txt diff --git a/Makefile b/Makefile index 34f4a7dd..56cf0dfe 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ endif @echo "deploy: build and upload to PyPi" @echo "tag: tag the repository with the current version\n" +version: + @hatch version + install: ./install.sh --unstable @@ -47,7 +50,7 @@ pytest: nopost: @bash check.sh --nopost -tag: +tag: version git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" build: check diff --git a/install.sh b/install.sh index 495919b4..61f1a4a6 100755 --- a/install.sh +++ b/install.sh @@ -58,11 +58,6 @@ find_config() { if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then fatal "Could not find $CONFIG_FILE!" fi - else - if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then - warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE" - warning "You might want to fix this!" - fi fi inform "Using $CONFIG_FILE in $CONFIG_DIR" } @@ -171,6 +166,12 @@ function pip_pkg_install { check_for_error } +function pip_requirements_install { + # A null Keyring prevents pip stalling in the background + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install -r "$@" + check_for_error +} + while [[ $# -gt 0 ]]; do K="$1" case $K in @@ -340,6 +341,15 @@ fi printf "\n" +if [ -f "requirements-examples.txt" ]; then + if confirm "Would you like to install example dependencies?"; then + inform "Installing dependencies from requirements-examples.txt..." + pip_requirements_install requirements-examples.txt + fi +fi + +printf "\n" + # Use pdoc to generate basic documentation from the installed module if confirm "Would you like to generate documentation?"; then diff --git a/pyproject.toml b/pyproject.toml index 7c565bb5..057079f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,13 +41,7 @@ dependencies = [ "pms5003 >= 1.0.1", "ltr559 >= 1.0.0", "st7735 >= 1.0.0", - "ads1015 >= 1.0.0", - "fonts", - "font-roboto", - "astral", - "pytz", - "sounddevice", - "paho-mqtt" + "ads1015 >= 1.0.0" ] [project.urls] diff --git a/requirements-examples.txt b/requirements-examples.txt new file mode 100644 index 00000000..b61fd7f5 --- /dev/null +++ b/requirements-examples.txt @@ -0,0 +1,7 @@ +fonts +font-roboto +astral +pytz +sounddevice +paho-mqtt + diff --git a/tox.ini b/tox.ini index 44c86546..2b6d87b8 100644 --- a/tox.ini +++ b/tox.ini @@ -20,15 +20,8 @@ commands = python -m build --no-isolation python -m twine check dist/* isort --check . - ruff . + ruff check . codespell . deps = - check-manifest - ruff - codespell - isort - twine - build - hatch - hatch-fancy-pypi-readme + -r{toxinidir}/requirements-dev.txt From 70947aa09406f5cfebce4b78b6630a0b48ec9919 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 18 Mar 2025 16:38:08 +0000 Subject: [PATCH 68/69] Add pillow to example requirements. --- requirements-examples.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-examples.txt b/requirements-examples.txt index b61fd7f5..96def717 100644 --- a/requirements-examples.txt +++ b/requirements-examples.txt @@ -4,4 +4,4 @@ astral pytz sounddevice paho-mqtt - +pillow From e85fab90893f2ed34b4c9deeabfaecb83f1475d9 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 11 Jul 2025 14:55:38 +0100 Subject: [PATCH 69/69] Prefer pip binary installs for #154. --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 61f1a4a6..d2d973a9 100755 --- a/install.sh +++ b/install.sh @@ -162,13 +162,13 @@ function apt_pkg_install { function pip_pkg_install { # A null Keyring prevents pip stalling in the background - PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@" + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --prefer-binary --upgrade "$@" check_for_error } function pip_requirements_install { # A null Keyring prevents pip stalling in the background - PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install -r "$@" + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --prefer-binary -r "$@" check_for_error }