diff --git a/.gitignore b/.gitignore index 5824813..a6506fa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ library/debian/ .pytest_cache .tox .vscode/ +*.db diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..303500c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +default_language_version: + python: python3.11 + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace # Trims trailing whitespace from files. + - id: end-of-file-fixer # Ensures files end with a single newline. + - id: check-yaml # Checks for valid YAML syntax. + - id: check-toml # Checks for valid TOML syntax + - id: check-json # Checks for valid JSON syntax. + - id: check-added-large-files # Prevents committing large files. + - id: mixed-line-ending # Enforces consistent line endings. + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.13.1 + hooks: + # Run the linter. + - id: ruff-check + args: [ --fix ] + # Run the formatter. + - id: ruff-format + + - repo: https://github.com/sqlfluff/sqlfluff + rev: 3.1.0 + hooks: + - id: sqlfluff-fix # Lints and automatically fixes SQL files. + args: [--dialect, 'sqlite'] diff --git a/pyproject.toml b/pyproject.toml index 057079f..ac8d9e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "enviroplus" dynamic = ["version", "readme"] description = "Enviro pHAT Plus environmental monitoring add-on for Raspberry Pi" license = {file = "LICENSE"} -requires-python = ">= 3.7" +requires-python = ">= 3.9" authors = [ { name = "Philip Howard", email = "phil@pimoroni.com" }, ] @@ -38,10 +38,19 @@ dependencies = [ "gpiod >= 2.1.3", "gpiodevice >= 0.0.3", "pimoroni-bme280 >= 1.0.0", - "pms5003 >= 1.0.1", + "pms5003==0.0.5", "ltr559 >= 1.0.0", "st7735 >= 1.0.0", - "ads1015 >= 1.0.0" + "ads1015 >= 1.0.0", + "fonts", + "font-roboto", + "astral", + "pytz", + "sounddevice", + "paho-mqtt", + "numpy>=2.0.2", + "requests>=2.32.5", + "rpi-gpio>=0.7.1", ] [project.urls] @@ -137,3 +146,12 @@ commands = [ "sudo raspi-config nonint do_serial_cons 1", "sudo raspi-config nonint do_serial_hw 0" ] + +[[tool.uv.index]] +url = "https://www.piwheels.org/simple" +default = true + +[dependency-groups] +dev = [ + "pre-commit>=4.3.0", +] diff --git a/scripts/measure_and_send.py b/scripts/measure_and_send.py new file mode 100644 index 0000000..b225170 --- /dev/null +++ b/scripts/measure_and_send.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 + +import datetime +import sqlite3 +import logging +import argparse +import json + +# Configure basic logging, will be updated later with argparse +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + +try: + # This import is required to initialize the board + from enviroplus import gas +except ImportError: + logging.error("The 'enviroplus-python' library is not installed. Please install it by running: pip3 install enviroplus-python") + exit() + +try: + # The BME280 sensor for temperature, pressure, and humidity + from bme280 import BME280 +except ImportError: + logging.error("The 'bme280' library is not installed. Please install it by running: pip3 install enviroplus-python[bme280]") + exit() + +try: + # The LTR559 sensor for light + from ltr559 import LTR559 +except ImportError: + logging.error("The 'ltr559' library is not installed. Please install it by running: pip3 install enviroplus-python[ltr559]") + exit() + +try: + # The PMS5003 sensor for particulate matter + from pms5003 import PMS5003, ReadTimeoutError, SerialTimeoutError +except ImportError: + logging.error("The 'pms5003' library is not installed. Please install it by running: pip3 install pms5003") + exit() + +# Initialize the sensors +bme280 = BME280() +ltr559 = LTR559() +pms5003_sensor = PMS5003() + + +def create_database_table(db_file): + """ + Connects to the SQLite database and creates the 'measurements' table + if it doesn't already exist. + """ + conn = sqlite3.connect(db_file) + c = conn.cursor() + c.execute("""CREATE TABLE IF NOT EXISTS measurements ( + timestamp DATETIME, + temperature REAL, + pressure REAL, + humidity REAL, + light_lux REAL, + light_proximity REAL, + pm1_0_standard REAL, + pm2_5_standard REAL, + pm10_standard REAL, + pm1_0_env REAL, + pm2_5_env REAL, + pm10_env REAL, + oxidising REAL, + reducing REAL, + nh3 REAL + )""") + conn.commit() + conn.close() + + +def get_sensor_data(): + """ + Reads all available data from the Enviro+ sensors. + Handles potential errors with PMS5003 and gas sensor readings. + Returns a dictionary of sensor data. + """ + data = {} + + # BME280 readings + data["temperature"] = bme280.get_temperature() + data["pressure"] = bme280.get_pressure() + data["humidity"] = bme280.get_humidity() + + # LTR559 readings + data["light_lux"] = ltr559.get_lux() + data["light_proximity"] = ltr559.get_proximity() + + try: + # PMS5003 particulate matter readings + pms5003_data = pms5003_sensor.read() + data["pm1_0_standard"] = pms5003_data.pm_ug_per_m3(1.0, False) + data["pm2_5_standard"] = pms5003_data.pm_ug_per_m3(2.5, False) + data["pm10_standard"] = pms5003_data.pm_ug_per_m3(10, False) + data["pm1_0_env"] = pms5003_data.pm_ug_per_m3(1.0, True) + data["pm2_5_env"] = pms5003_data.pm_ug_per_m3(2.5, True) + data["pm10_env"] = pms5003_data.pm_ug_per_m3(None, True) + except (ReadTimeoutError, SerialTimeoutError): + # This can happen if the sensor is not ready + logging.warning("PMS5003 sensor read timeout. Skipping particulate matter data.") + data.update({"pm1_0_standard": None, "pm2_5_standard": None, "pm10_standard": None, "pm1_0_env": None, "pm2_5_env": None, "pm10_env": None}) + + # Gas sensor readings + gas_data = gas.read_all() + data["oxidising"] = gas_data.oxidising + data["reducing"] = gas_data.reducing + data["nh3"] = gas_data.nh3 + + return data + + +def log_data_local(data, db_file): + """ + Inserts a single set of sensor data into the SQLite database. + """ + conn = sqlite3.connect(db_file) + c = conn.cursor() + try: + c.execute( + "INSERT INTO measurements VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + data["timestamp"], + data["temperature"], + data["pressure"], + data["humidity"], + data["light_lux"], + data["light_proximity"], + data["pm1_0_standard"], + data["pm2_5_standard"], + data["pm10_standard"], + data["pm1_0_env"], + data["pm2_5_env"], + data["pm10_env"], + data["oxidising"], + data["reducing"], + data["nh3"], + ), + ) + conn.commit() + logging.info("Data logged successfully to local database.") + except Exception as e: + logging.error(f"Error logging data to local database: {e}") + finally: + conn.close() + + +def log_data_remote(data, url): + """ + Sends a single set of sensor data to a remote URL as a POST request. + """ + try: + import requests + + headers = {"Content-type": "application/json", "Accept": "text/plain"} + response = requests.post(url, data=json.dumps(data), headers=headers) + response.raise_for_status() # Raise an exception for bad status codes + logging.info(f"Data logged successfully to remote URL: {url}") + except requests.exceptions.RequestException as e: + logging.error(f"Error logging data to remote URL: {e}") + logging.warning("Skipping remote data logging.") + + +if __name__ == "__main__": + # Mapping for log levels + LOG_LEVELS = {"debug": logging.DEBUG, "info": logging.INFO, "warning": logging.WARNING, "error": logging.ERROR, "critical": logging.CRITICAL} + + parser = argparse.ArgumentParser(description="Log environmental data from an Enviro+ HAT to an SQLite database or a remote URL.") + parser.add_argument("--db-file", type=str, default="enviroplus_data.db", help="Specify the name of the local database file.") + parser.add_argument("--log-level", type=str, default="info", choices=list(LOG_LEVELS.keys()), help="Set the logging level (debug, info, warning, error, critical).") + parser.add_argument("--remote-url", type=str, default=None, help="Specify a remote URL to send the data to via a POST request.") + + args = parser.parse_args() + + # Update the logging level based on the argument + logging.getLogger().setLevel(LOG_LEVELS[args.log_level]) + + logging.info("Starting Enviro+ data logger.") + + timestamp = datetime.datetime.now().isoformat() + + logging.info(f"Reading sensor data for timestamp: {timestamp}") + sensor_data = get_sensor_data() + sensor_data["timestamp"] = timestamp + + logging.debug(f"Sensor data read: {sensor_data}") + + if args.remote_url: + log_data_remote(sensor_data, args.remote_url) + else: + create_database_table(args.db_file) + log_data_local(sensor_data, args.db_file) + + logging.info("Exiting.") diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..92df100 --- /dev/null +++ b/uv.lock @@ -0,0 +1,478 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "ads1015" +version = "1.0.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "i2cdevice" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/ads1015/ads1015-1.0.0-py3-none-any.whl", hash = "sha256:3497a7457f045b4abd049e96ab3a5ff95603a3b08b36273e4dfa6234964130df" }, +] + +[[package]] +name = "astral" +version = "3.2" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/astral/astral-3.2-py3-none-any.whl", hash = "sha256:acda65422041785b98750ec7ed6cf945a5dc3aabe53c64c4cc77aa7d951bfad9" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/certifi/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:c7539e56013898721866606c678492a26f18c87993f875256d7b3173f4d013f8" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp311-cp311-linux_armv6l.whl", hash = "sha256:da2e6428aa89f81b9a97c5818e7e5875f2c40bef6abbbe17e0d768199aa943f1" }, + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:da2e6428aa89f81b9a97c5818e7e5875f2c40bef6abbbe17e0d768199aa943f1" }, + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp313-cp313-linux_armv6l.whl", hash = "sha256:58f31ade45b0247d0a54d3003ac3c39da0851f86252d624836e138628ef89c1d" }, + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:58f31ade45b0247d0a54d3003ac3c39da0851f86252d624836e138628ef89c1d" }, + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp39-cp39-linux_armv6l.whl", hash = "sha256:df27fb06fb48fa1ca9b2a87f05bae8ff3edc0ac5d8c65e8f77df3f9987a1b36b" }, + { url = "https://www.piwheels.org/simple/cffi/cffi-1.17.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:df27fb06fb48fa1ca9b2a87f05bae8ff3edc0ac5d8c65e8f77df3f9987a1b36b" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/cfgv/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:a26cde6f2dbc2fb41e75d59fe67f9a9deb9c0257659fd357194e97f3e0891297" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/charset-normalizer/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:89e3250ea717e9475cf0f59a233369a175dbd1949bd78d2056f7937c19d05995" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/distlib/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9306a9f4fd8333742f84234ce68f10b295a2ca58df9a06df8f62a76cad95a6d6" }, +] + +[[package]] +name = "enviroplus" +source = { editable = "." } +dependencies = [ + { name = "ads1015" }, + { name = "astral" }, + { name = "font-roboto" }, + { name = "fonts" }, + { name = "gpiod" }, + { name = "gpiodevice" }, + { name = "ltr559" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "paho-mqtt" }, + { name = "pimoroni-bme280" }, + { name = "pms5003" }, + { name = "pytz" }, + { name = "requests" }, + { name = "rpi-gpio" }, + { name = "sounddevice" }, + { name = "st7735" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, +] + +[package.metadata] +requires-dist = [ + { name = "ads1015", specifier = ">=1.0.0" }, + { name = "astral" }, + { name = "font-roboto" }, + { name = "fonts" }, + { name = "gpiod", specifier = ">=2.1.3" }, + { name = "gpiodevice", specifier = ">=0.0.3" }, + { name = "ltr559", specifier = ">=1.0.0" }, + { name = "numpy", specifier = ">=2.0.2" }, + { name = "paho-mqtt" }, + { name = "pimoroni-bme280", specifier = ">=1.0.0" }, + { name = "pms5003", specifier = "==0.0.5" }, + { name = "pytz" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "rpi-gpio", specifier = ">=0.7.1" }, + { name = "sounddevice" }, + { name = "st7735", specifier = ">=1.0.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pre-commit", specifier = ">=4.3.0" }] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/filelock/filelock-3.19.1-py3-none-any.whl", hash = "sha256:c6a32d7b7d0e25d99e92dddf3d8364467d65a9eb01208deda4292b26ca7346b0" }, +] + +[[package]] +name = "font-roboto" +version = "0.0.1" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/font-roboto/font_roboto-0.0.1-py3-none-any.whl", hash = "sha256:2caa02403b2b6534229dd652205b1b2cacabc6d00e0a75b25f4c906cdb902d1d" }, +] + +[[package]] +name = "fonts" +version = "0.0.3" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/fonts/fonts-0.0.3-py3-none-any.whl", hash = "sha256:e5f551379088ab260c2537980c3ccdff8af93408d9d4fa3319388d2ee25b7b6d" }, +] + +[[package]] +name = "gpiod" +version = "2.3.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp311-cp311-linux_armv6l.whl", hash = "sha256:d69aa31fceb26920480211ae36f771247a2c17f493282325dd07c16d37325e3d" }, + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d69aa31fceb26920480211ae36f771247a2c17f493282325dd07c16d37325e3d" }, + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp313-cp313-linux_armv6l.whl", hash = "sha256:ca0baede2f0ae751a65cc8d5f820d9c13c98f4f2d0606e0a81ca285658f03705" }, + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ca0baede2f0ae751a65cc8d5f820d9c13c98f4f2d0606e0a81ca285658f03705" }, + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp39-cp39-linux_armv6l.whl", hash = "sha256:4a1924a5ef735073dc49c0d4934a1827d19a3c61ddf839a6a8b6f40ea0d9ac37" }, + { url = "https://www.piwheels.org/simple/gpiod/gpiod-2.3.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4a1924a5ef735073dc49c0d4934a1827d19a3c61ddf839a6a8b6f40ea0d9ac37" }, +] + +[[package]] +name = "gpiodevice" +version = "0.0.5" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "gpiod" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/gpiodevice/gpiodevice-0.0.5-py3-none-any.whl", hash = "sha256:b808250cd1d89379b69f98cc6442b6b098aa32604c3ed7fb194a39607ee35253" }, +] + +[[package]] +name = "i2cdevice" +version = "1.0.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "smbus2" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/i2cdevice/i2cdevice-1.0.0-py3-none-any.whl", hash = "sha256:595cfa3815aab4d8a4bbe1cbd4a4e1271d56cf7c1de367eab6a94dd80566f0c1" }, +] + +[[package]] +name = "identify" +version = "2.6.14" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/identify/identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:049ae33656cdba419bfd4ccd42058dab90fb7c26c4f529e34d3c21d7e5835b88" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/idna/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" }, +] + +[[package]] +name = "ltr559" +version = "1.0.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "i2cdevice" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/ltr559/ltr559-1.0.0-py3-none-any.whl", hash = "sha256:0805f2d9ab43b2f622847ec71cb6fbca2b91b396aafb121d911172a366a56c3d" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/nodeenv/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:e3d5a1b67a0cdf73b094aca8a68a80befbde9ca317303f18bf8af61198edc37f" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://www.piwheels.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +wheels = [ + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp311-cp311-linux_armv6l.whl", hash = "sha256:a0c5c3ea11329e36d0e792d4859f3bfc05e820d56212c1be6a5ed5ba121eb548" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:a0c5c3ea11329e36d0e792d4859f3bfc05e820d56212c1be6a5ed5ba121eb548" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp313-cp313-linux_armv6l.whl", hash = "sha256:364a2d42d4a405e55e5d8dee0eafe843c75a53a299fd5e382185b3333c848df1" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:364a2d42d4a405e55e5d8dee0eafe843c75a53a299fd5e382185b3333c848df1" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp39-cp39-linux_armv6l.whl", hash = "sha256:27740e75344b7522f6be4c3d694775369524f12e3b31327a3642a35f5b416db8" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.0.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:27740e75344b7522f6be4c3d694775369524f12e3b31327a3642a35f5b416db8" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://www.piwheels.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +wheels = [ + { url = "https://www.piwheels.org/simple/numpy/numpy-2.2.6-cp311-cp311-linux_armv6l.whl", hash = "sha256:9d5168644d4f18fda98839f03a5e6ede9b1b780ac4a187c188971a3dc4b0382e" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.2.6-cp311-cp311-linux_armv7l.whl", hash = "sha256:9d5168644d4f18fda98839f03a5e6ede9b1b780ac4a187c188971a3dc4b0382e" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.2.6-cp313-cp313-linux_armv6l.whl", hash = "sha256:495b42b52f5be434ff56e549d9deac19ac50de272f1f803755408e21c5e5e8cc" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.2.6-cp313-cp313-linux_armv7l.whl", hash = "sha256:495b42b52f5be434ff56e549d9deac19ac50de272f1f803755408e21c5e5e8cc" }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://www.piwheels.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +wheels = [ + { url = "https://www.piwheels.org/simple/numpy/numpy-2.3.2-cp311-cp311-linux_armv6l.whl", hash = "sha256:6ead1855f873ab7d5b31a9a58c8f6f01fa64a310a5c2e7977fbf128827a506de" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.3.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:6ead1855f873ab7d5b31a9a58c8f6f01fa64a310a5c2e7977fbf128827a506de" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.3.2-cp313-cp313-linux_armv6l.whl", hash = "sha256:db3d97f6938575f39bbdeacbc8da58912b34857cf3762a3c308b237e4cbc4d0c" }, + { url = "https://www.piwheels.org/simple/numpy/numpy-2.3.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:db3d97f6938575f39bbdeacbc8da58912b34857cf3762a3c308b237e4cbc4d0c" }, +] + +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/paho-mqtt/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee" }, +] + +[[package]] +name = "pimoroni-bme280" +version = "1.0.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "i2cdevice" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/pimoroni-bme280/pimoroni_bme280-1.0.0-py3-none-any.whl", hash = "sha256:7415a037ba3dd14ce40d705bb006e02f53b276c72aef75fda5ea86daef666a4f" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/platformdirs/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85" }, +] + +[[package]] +name = "pms5003" +version = "0.0.5" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "pyserial" }, + { name = "setuptools" }, +] +wheels = [ + { url = "https://archive1.piwheels.org/simple/pms5003/pms5003-0.0.5-py3-none-any.whl", hash = "sha256:71faeada77d9d514304b6fa7d830d3bc17d626c605871c7c0ae7fee47b7976f4" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/pre-commit/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:12b5005e05af01759b1dbd7491b6e2400ab9800c25ddea1f7f8ee892c9bd5a41" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/pycparser/pycparser-2.22-py3-none-any.whl", hash = "sha256:1a6dcf2544745c9222d3981d9ffcd6231ffc69b827b0847fc40586388287a0ca" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/pyserial/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/pytz/pytz-2025.2-py3-none-any.whl", hash = "sha256:1e36035a3344defebdbea3c67ac072599349642911e39b51499444e0f87ee562" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/pyyaml/PyYAML-6.0.2-cp311-cp311-linux_armv6l.whl", hash = "sha256:4a116b1e66edb7eea662c281c85f5669606bdccb6173b03d442a0dd33586c8cc" }, + { url = "https://www.piwheels.org/simple/pyyaml/PyYAML-6.0.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:4a116b1e66edb7eea662c281c85f5669606bdccb6173b03d442a0dd33586c8cc" }, + { url = "https://www.piwheels.org/simple/pyyaml/PyYAML-6.0.2-cp39-cp39-linux_armv6l.whl", hash = "sha256:c17d542ee2e07d5085fd1a99af3c9bd7d313b0f26527f4fb2cdc5b553f3f5af2" }, + { url = "https://www.piwheels.org/simple/pyyaml/PyYAML-6.0.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c17d542ee2e07d5085fd1a99af3c9bd7d313b0f26527f4fb2cdc5b553f3f5af2" }, + { url = "https://www.piwheels.org/simple/pyyaml/pyyaml-6.0.2-cp313-cp313-linux_armv6l.whl", hash = "sha256:bb46ee84f353e4275d4053cea305eeb1bf01bbb4cf3a08dbca7eb1a2562419fd" }, + { url = "https://www.piwheels.org/simple/pyyaml/pyyaml-6.0.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:bb46ee84f353e4275d4053cea305eeb1bf01bbb4cf3a08dbca7eb1a2562419fd" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/requests/requests-2.32.5-py3-none-any.whl", hash = "sha256:d1bc11064d1d734baeb61467e3ec2631fa3f77d7d4836c33aee0a6e63f2c0fda" }, +] + +[[package]] +name = "rpi-gpio" +version = "0.7.1" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/rpi-gpio/RPi.GPIO-0.7.1-cp311-cp311-linux_armv6l.whl", hash = "sha256:df02c92d1a55ff8cdfffea5012ea73a9b09fdcc4d14799d834b3840fcf4039ad" }, + { url = "https://www.piwheels.org/simple/rpi-gpio/RPi.GPIO-0.7.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:df02c92d1a55ff8cdfffea5012ea73a9b09fdcc4d14799d834b3840fcf4039ad" }, + { url = "https://www.piwheels.org/simple/rpi-gpio/RPi.GPIO-0.7.1-cp39-cp39-linux_armv6l.whl", hash = "sha256:231f6142c25975a7e39b96faf4905f05f97dba6809e8c5f0d58f284b35b4b821" }, + { url = "https://www.piwheels.org/simple/rpi-gpio/RPi.GPIO-0.7.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:231f6142c25975a7e39b96faf4905f05f97dba6809e8c5f0d58f284b35b4b821" }, + { url = "https://www.piwheels.org/simple/rpi-gpio/rpi_gpio-0.7.1-cp313-cp313-linux_armv6l.whl", hash = "sha256:fc1fc50ba7dd5ac5ec792f68012763a8548c4520047e1be0f3b8a077a7aa01f8" }, + { url = "https://www.piwheels.org/simple/rpi-gpio/rpi_gpio-0.7.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc1fc50ba7dd5ac5ec792f68012763a8548c4520047e1be0f3b8a077a7aa01f8" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/setuptools/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:3d209672d008f035ec80af944dfa381396b57754a83983d30d0bab717e7407e4" }, +] + +[[package]] +name = "smbus2" +version = "0.5.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/smbus2/smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:55af969c2b4336d0ea205101de5840c88275aac0520141e87a9792d5707e3355" }, +] + +[[package]] +name = "sounddevice" +version = "0.5.2" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "cffi" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/sounddevice/sounddevice-0.5.2-py3-none-any.whl", hash = "sha256:abdb30495e6b4bd74beb5963824bbe1a9bdf15d081bc13e552d15938995bca67" }, +] + +[[package]] +name = "spidev" +version = "3.7" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp311-cp311-linux_armv6l.whl", hash = "sha256:5557137ac66cf5af202a192dec9717c8ece331c2cf5792aec8699363e38fe1d2" }, + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp311-cp311-linux_armv7l.whl", hash = "sha256:5557137ac66cf5af202a192dec9717c8ece331c2cf5792aec8699363e38fe1d2" }, + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp313-cp313-linux_armv6l.whl", hash = "sha256:e78a51adda53bfacc5c13ee8f82e028d78a22836aeefa8e4cf8f7f4829fd07d8" }, + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp313-cp313-linux_armv7l.whl", hash = "sha256:e78a51adda53bfacc5c13ee8f82e028d78a22836aeefa8e4cf8f7f4829fd07d8" }, + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp39-cp39-linux_armv6l.whl", hash = "sha256:50e5d4bcf5a81572e0f2cbe67dcb9ca89c8d2a80dcc4ad4dbeb4eb3523d535c4" }, + { url = "https://www.piwheels.org/simple/spidev/spidev-3.7-cp39-cp39-linux_armv7l.whl", hash = "sha256:50e5d4bcf5a81572e0f2cbe67dcb9ca89c8d2a80dcc4ad4dbeb4eb3523d535c4" }, +] + +[[package]] +name = "st7735" +version = "1.0.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://www.piwheels.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "spidev" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/st7735/st7735-1.0.0-py3-none-any.whl", hash = "sha256:20eb580e3d2462d8498ea5922cb5c1128abc46cbbc91998f07911054b3594f83" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/typing-extensions/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/tzdata/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:4e87b5840916dc14fd650d261981d8b944d6db8962b4d22478591ccdc3bd69bd" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://www.piwheels.org/simple" } +wheels = [ + { url = "https://www.piwheels.org/simple/urllib3/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:1b2cba9e21317a909f95221578fc769fc0fb043ed28a114be1650e89be69994c" }, +] + +[[package]] +name = "virtualenv" +version = "20.34.0" +source = { registry = "https://www.piwheels.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +wheels = [ + { url = "https://www.piwheels.org/simple/virtualenv/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:0a73d39f35089d02d12bc12e81a1c8ca4142e96be2c61cbcc86b871dc8bd3156" }, +]