diff --git a/README.md b/README.md index 53138be..11ff843 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ A simple Air Quality Index monitor, designed to work on the Raspberry Pi with the SDS011 Nova PM Sensor. - [AQIMON](#aqimon) + - [Screenshot](#screenshot) - [Installation](#installation) - [Pre-Requisites](#pre-requisites) - [Install](#install) @@ -15,6 +16,10 @@ A simple Air Quality Index monitor, designed to work on the Raspberry Pi with th - [Using the Mock Reader](#using-the-mock-reader) - [Submitting a PR](#submitting-a-pr) +## Screenshot + +![Screenshot](screenshot.png) + ## Installation ### Pre-Requisites diff --git a/aqimon/read/novapm.py b/aqimon/read/novapm.py index c79f296..2abd286 100644 --- a/aqimon/read/novapm.py +++ b/aqimon/read/novapm.py @@ -7,7 +7,7 @@ from . import AqiRead, ReaderState, ReaderStatus import serial from typing import Union -from .sds011 import QueryModeReader +from sds011lib import SDS011QueryReader from statistics import mean @@ -26,7 +26,7 @@ def __init__( if isinstance(ser_dev, str): ser_dev = serial.Serial(ser_dev, timeout=2) - self.reader = QueryModeReader(ser_dev=ser_dev, send_command_sleep=command_wait_time) + self.reader = SDS011QueryReader(ser_dev=ser_dev, send_command_sleep=command_wait_time) # Initial the reader to be in the mode we want. self.reader.wake() diff --git a/aqimon/read/sds011/__init__.py b/aqimon/read/sds011/__init__.py deleted file mode 100644 index 37314d9..0000000 --- a/aqimon/read/sds011/__init__.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Nova PM SDS011 Reader module. - -Device: https://www.amazon.com/SDS011-Quality-Detection-Conditioning-Monitor/dp/B07FSDMRR5 - -Spec: https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf -Spec: https://cdn-reichelt.de/documents/datenblatt/X200/SDS011-DATASHEET.pdf -""" - -import serial -import time -from .responses import ( - QueryReadResponse, - ReportingModeReadResponse, - SleepWakeReadResponse, - DeviceIdResponse, - CheckFirmwareReadResponse, - WorkingPeriodReadResponse, -) -from . import constants as con -from .exceptions import IncompleteReadException, IncorrectCommandException, IncorrectCommandCodeException - - -class NovaPmReader: - """NOVA PM SDS011 Reader.""" - - def __init__(self, ser_dev: serial.Serial, send_command_sleep: int = 1): - """Create the device.""" - self.ser = ser_dev - self.send_command_sleep = send_command_sleep - - def request_data(self) -> None: - """Request device to return pollutant data.""" - cmd = con.Command.QUERY.value + (b"\x00" * 12) + con.ALL_SENSOR_ID - self._send_command(cmd) - - def query_data(self) -> QueryReadResponse: - """Query the device for pollutant data.""" - return QueryReadResponse(self._read_response()) - - def request_reporting_mode(self) -> None: - """Request device to return the current reporting mode.""" - cmd = ( - con.Command.SET_REPORTING_MODE.value - + con.OperationType.QUERY.value - + con.ReportingMode.ACTIVE.value - + (b"\x00" * 10) - + con.ALL_SENSOR_ID - ) - self._send_command(cmd) - - def query_reporting_mode(self) -> ReportingModeReadResponse: - """Get the current reporting mode of the device.""" - return ReportingModeReadResponse(self._read_response()) - - def set_active_mode(self) -> None: - """Set the reporting mode to active.""" - self._set_reporting_mode(con.ReportingMode.ACTIVE) - try: - self.query_reporting_mode() - except IncorrectCommandException: - pass - except IncompleteReadException: - pass - - def set_query_mode(self) -> None: - """Set the reporting mode to querying.""" - self._set_reporting_mode(con.ReportingMode.QUERYING) - try: - self.query_reporting_mode() - except IncorrectCommandException: - pass - except IncompleteReadException: - pass - except IncorrectCommandCodeException: - pass - - def _set_reporting_mode(self, reporting_mode: con.ReportingMode) -> None: - """Set the reporting mode, either ACTIVE or QUERYING. - - ACTIVE mode means the device will always return a Query command response when data is asked for, regardless of - what command was sent. - - QUERYING mode means the device will only return responses to submitted commands, even for Query commands. - - ACTIVE mode is the factory default, but generally, QUERYING mode is preferrable for the longevity of the device. - """ - cmd = ( - con.Command.SET_REPORTING_MODE.value - + con.OperationType.SET_MODE.value - + reporting_mode.value - + (b"\x00" * 10) - + con.ALL_SENSOR_ID - ) - self._send_command(cmd) - # Switching between reporting modes is finicky; resetting the serial connection seems to address issues. - self.ser.close() - self.ser.open() - - def request_sleep_state(self) -> None: - """Get the current sleep state.""" - cmd = con.Command.SET_SLEEP.value + con.OperationType.QUERY.value + b"\x00" + (b"\x00" * 10) + con.ALL_SENSOR_ID - self._send_command(cmd) - - def query_sleep_state(self) -> SleepWakeReadResponse: - """Get the current sleep state.""" - return SleepWakeReadResponse(self._read_response()) - - def set_sleep_state(self, sleep_state: con.SleepState) -> None: - """Set the sleep state, either wake or sleep.""" - cmd = ( - con.Command.SET_SLEEP.value - + con.OperationType.SET_MODE.value - + sleep_state.value - + (b"\x00" * 10) - + con.ALL_SENSOR_ID - ) - self._send_command(cmd) - - def sleep(self) -> None: - """Put the device to sleep, turning off fan and diode.""" - self.set_sleep_state(con.SleepState.SLEEP) - - def wake(self) -> None: - """Wake the device up to start reading.""" - self.set_sleep_state(con.SleepState.WORK) - - def safe_wake(self) -> None: - """Wake the device up, if you don't know what mode its in. - - This operates as a fire-and-forget, even in query mode. You shouldn't have to (and can't) query for a response - after this command. - """ - self.wake() - # If we were in query mode, this would flush out the response. If in active mode, this would be return read - # data, but we don't care. - self.ser.read(10) - - def set_device_id(self, device_id: bytes, target_device_id: bytes = con.ALL_SENSOR_ID) -> None: - """Set the device ID.""" - if len(device_id) != 2 or len(target_device_id) != 2: - raise AttributeError(f"Device ID must be 4 bytes, found {len(device_id)}, and {len(target_device_id)}") - cmd = con.Command.SET_DEVICE_ID.value + (b"\x00" * 10) + device_id + target_device_id - self._send_command(cmd) - - def query_device_id(self) -> DeviceIdResponse: - """Set the device ID.""" - return DeviceIdResponse(self._read_response()) - - def request_working_period(self) -> None: - """Retrieve the current working period for the device.""" - cmd = con.Command.SET_WORKING_PERIOD.value + con.OperationType.QUERY.value + (b"\x00" * 11) + con.ALL_SENSOR_ID - self._send_command(cmd) - - def query_working_period(self) -> WorkingPeriodReadResponse: - """Retrieve the current working period for the device.""" - return WorkingPeriodReadResponse(self._read_response()) - - def set_working_period(self, working_period: int) -> None: - """Set the working period for the device. - - Working period must be between 0 and 30. - - 0 means the device will read continuously. - Any value 1-30 means the device will wake and read for 30 seconds every n*60-30 seconds. - """ - if 0 >= working_period >= 30: - raise AttributeError("Working period must be between 0 and 30") - cmd = ( - con.Command.SET_WORKING_PERIOD.value - + con.OperationType.SET_MODE.value - + bytes([working_period]) - + (b"\x00" * 10) - + con.ALL_SENSOR_ID - ) - self._send_command(cmd) - - def request_firmware_version(self) -> None: - """Retrieve the firmware version from the device.""" - cmd = con.Command.CHECK_FIRMWARE_VERSION.value + (b"\x00" * 12) + con.ALL_SENSOR_ID - self._send_command(cmd) - - def query_firmware_version(self) -> CheckFirmwareReadResponse: - """Retrieve the firmware version from the device.""" - return CheckFirmwareReadResponse(self._read_response()) - - def _send_command(self, cmd: bytes): - """Send a command to the device as bytes.""" - head = con.HEAD + con.SUBMIT_TYPE - full_command = head + cmd + bytes([self._cmd_checksum(cmd)]) + con.TAIL - if len(full_command) != 19: - raise Exception(f"Command length must be 19, but was {len(full_command)}") - self.ser.write(full_command) - time.sleep(self.send_command_sleep) - - def _read_response(self) -> bytes: - """Read a response from the device.""" - result = self.ser.read(10) - if len(result) != 10: - raise IncompleteReadException(len(result)) - return result - - def _cmd_checksum(self, data: bytes) -> int: - """Generate a checksum for the data bytes of a command.""" - if len(data) != 15: - raise AttributeError("Invalid checksum length.") - return sum(d for d in data) % 256 - - -class QueryModeReader: - """Reader working in query mode.""" - - def __init__(self, ser_dev: serial.Serial, send_command_sleep: int = 1): - """Create the device.""" - self.base_reader = NovaPmReader(ser_dev=ser_dev, send_command_sleep=send_command_sleep) - self.base_reader.safe_wake() - self.base_reader.set_query_mode() - - def query(self) -> QueryReadResponse: - """Query the device for pollutant data.""" - self.base_reader.request_data() - return self.base_reader.query_data() - - def get_reporting_mode(self) -> ReportingModeReadResponse: - """Get the current reporting mode of the device.""" - self.base_reader.request_reporting_mode() - return self.base_reader.query_reporting_mode() - - def get_sleep_state(self) -> SleepWakeReadResponse: - """Get the current sleep state.""" - self.base_reader.request_sleep_state() - return self.base_reader.query_sleep_state() - - def sleep(self) -> SleepWakeReadResponse: - """Put the device to sleep, turning off fan and diode.""" - self.base_reader.sleep() - return self.base_reader.query_sleep_state() - - def wake(self) -> SleepWakeReadResponse: - """Wake the device up to start reading.""" - self.base_reader.wake() - return self.base_reader.query_sleep_state() - - def set_device_id(self, device_id: bytes, target_device_id: bytes = con.ALL_SENSOR_ID) -> DeviceIdResponse: - """Set the device ID.""" - self.base_reader.set_device_id(device_id, target_device_id) - return self.base_reader.query_device_id() - - def get_working_period(self) -> WorkingPeriodReadResponse: - """Retrieve the current working period for the device.""" - self.base_reader.request_working_period() - return self.base_reader.query_working_period() - - def set_working_period(self, working_period: int) -> WorkingPeriodReadResponse: - """Set the working period for the device. - - Working period must be between 0 and 30. - - 0 means the device will read continuously. - Any value 1-30 means the device will wake and read for 30 seconds every n*60-30 seconds. - """ - self.base_reader.set_working_period(working_period) - return self.base_reader.query_working_period() - - def get_firmware_version(self) -> CheckFirmwareReadResponse: - """Retrieve the firmware version from the device.""" - self.base_reader.request_firmware_version() - return self.base_reader.query_firmware_version() - - -class ActiveModeReader: - """Active Mode Reader. - - Use with caution! Active mode is unpredictable. Query mode is much preferred. - """ - - def __init__(self, ser_dev: serial.Serial, send_command_sleep: int = 2): - """Create the device.""" - self.base_reader = NovaPmReader(ser_dev=ser_dev, send_command_sleep=send_command_sleep) - self.ser_dev = ser_dev - self.base_reader.safe_wake() - self.base_reader.set_active_mode() - - def query(self) -> QueryReadResponse: - """Query the device for pollutant data.""" - return self.base_reader.query_data() - - def sleep(self) -> None: - """Put the device to sleep, turning off fan and diode.""" - self.base_reader.sleep() - - # Sleep seems to behave very strangely in active mode. It continually outputs data for old commands for quite - # a while before eventually having nothing to report. This forces it to "drain" whatever it was doing before - # returning, but also feels quite dangerous. - while len(self.ser_dev.read(10)) == 10: - pass - - def wake(self) -> None: - """Wake the device up to start reading.""" - self.base_reader.wake() - self.ser_dev.read(10) - - def set_device_id(self, device_id: bytes, target_device_id: bytes = con.ALL_SENSOR_ID) -> None: - """Set the device ID.""" - self.base_reader.set_device_id(device_id, target_device_id) - self.ser_dev.read(10) - - def set_working_period(self, working_period: int) -> None: - """Set the working period for the device. - - Working period must be between 0 and 30. - - 0 means the device will read continuously. - Any value 1-30 means the device will wake and read for 30 seconds every n*60-30 seconds. - """ - self.base_reader.set_working_period(working_period) - self.ser_dev.read(10) diff --git a/aqimon/read/sds011/constants.py b/aqimon/read/sds011/constants.py deleted file mode 100644 index 482f573..0000000 --- a/aqimon/read/sds011/constants.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Byte constants for the SDS011 device.""" -from enum import Enum - - -# Message head constant -HEAD = b"\xaa" -# Message tail constant -TAIL = b"\xab" - -# ID to send if the command should be issued to all sensor IDs. -ALL_SENSOR_ID = b"\xff\xff" - -# The submit type -SUBMIT_TYPE = b"\xb4" - - -class ResponseType(Enum): - """Response types for commands. - - GENERAL_RESPONSE is for all commands except query. - QUERY_RESPONSE only applies to the query command. - """ - - GENERAL_RESPONSE = b"\xc5" - # Query command has its own response type. - QUERY_RESPONSE = b"\xc0" - - -class Command(Enum): - """Possible commands for the device.""" - - SET_REPORTING_MODE = b"\x02" - QUERY = b"\x04" - SET_DEVICE_ID = b"\x05" - SET_SLEEP = b"\x06" - SET_WORKING_PERIOD = b"\x08" - CHECK_FIRMWARE_VERSION = b"\x07" - - -class OperationType(Enum): - """Operation type for many commands. - - Many commands have two modes, one for setting a value, and another for retrieving. - """ - - QUERY = b"\x00" - SET_MODE = b"\x01" - - -class ReportingMode(Enum): - """Reporting mode for the device. - - ACTIVE mode means that the device is constantly returning read data from the device, and won't respond correctly - to other query requests. - - QUERYING mode means that the device won't return read data unless explicitly asked for it. - """ - - ACTIVE = b"\x00" - QUERYING = b"\x01" - - -class SleepState(Enum): - """State of the device, either working or sleeping.""" - - SLEEP = b"\x00" - WORK = b"\x01" diff --git a/aqimon/read/sds011/exceptions.py b/aqimon/read/sds011/exceptions.py deleted file mode 100644 index 51ef046..0000000 --- a/aqimon/read/sds011/exceptions.py +++ /dev/null @@ -1,55 +0,0 @@ -"""All exception classes for the SDS011.""" - - -class Sds011Exception(Exception): - """Base exception for SDS011 device.""" - - pass - - -class ChecksumFailedException(Sds011Exception): - """Thrown if the checksum value in a response is incorrect.""" - - def __init__(self, expected: int, actual: int): - """Create exception.""" - super().__init__() - self.expected = expected - self.actual = actual - - -class IncorrectCommandException(Sds011Exception): - """Thrown if the command ID in a response is incorrect.""" - - def __init__(self, expected: int, actual: int): - """Create exception.""" - super().__init__(f"Expected command {expected}, found {actual}") - self.expected = expected - self.actual = actual - - -class IncorrectCommandCodeException(Sds011Exception): - """Thrown if the command code in a response is incorrect.""" - - def __init__(self, expected: int, actual: int): - """Create exception.""" - super().__init__(f"Expected code {expected}, found {actual}") - self.expected = expected - self.actual = actual - - -class IncorrectWrapperException(Sds011Exception): - """Thrown if the wrapper of a response (either HEAD or TAIL) is incorrect.""" - - pass - - -class IncompleteReadException(Sds011Exception): - """Thrown if the device didn't return complete data when asking for a response.""" - - pass - - -class InvalidDeviceIdException(Sds011Exception): - """Thrown if the trying to set the device ID on an invalid device.""" - - pass diff --git a/aqimon/read/sds011/responses.py b/aqimon/read/sds011/responses.py deleted file mode 100644 index 4ae32a5..0000000 --- a/aqimon/read/sds011/responses.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Response objects for SDS011. - -Creates and validates typed classes from binary responses from the device. -""" -from .constants import ( - HEAD, - TAIL, - Command, - ResponseType, - SleepState, - OperationType, - ReportingMode, -) -from .exceptions import ( - ChecksumFailedException, - IncorrectCommandException, - IncorrectCommandCodeException, - IncorrectWrapperException, - IncompleteReadException, -) - - -class ReadResponse: - """Generic read response object for responses from SDS011.""" - - def __init__(self, data: bytes, command_code: Command, response_type: ResponseType = ResponseType.GENERAL_RESPONSE): - """Create a read response.""" - if len(data) != 10: - raise IncompleteReadException() - - self.head = data[0:1] - self.cmd_id = data[1:2] - self.data = data[2:8] - self.device_id = data[6:8] - self.checksum: int = data[8] - self.tail = data[9:10] - self.expected_command_code = command_code - self.expected_response_type = response_type - # Check it! - self.verify() - - def verify(self): - """Verify the read data.""" - if self.head != HEAD: - raise IncorrectWrapperException() - if self.tail != TAIL: - raise IncorrectWrapperException() - if self.checksum != self.calc_checksum(): - raise ChecksumFailedException(expected=self.checksum, actual=self.calc_checksum()) - if self.cmd_id != self.expected_response_type.value: - raise IncorrectCommandException(expected=self.expected_response_type.value, actual=self.cmd_id) - - # Query responses don't validate the command code - if ( - self.expected_response_type != ResponseType.QUERY_RESPONSE - and bytes([self.data[0]]) != self.expected_command_code.value - ): - raise IncorrectCommandCodeException(expected=self.expected_command_code.value, actual=self.data[0]) - - def calc_checksum(self) -> int: - """Calculate the checksum for the read data.""" - return sum(d for d in self.data) % 256 - - -class QueryReadResponse(ReadResponse): - """Query read response.""" - - def __init__(self, data: bytes): - """Create a query read response.""" - super().__init__(data, command_code=Command.QUERY, response_type=ResponseType.QUERY_RESPONSE) - - self.pm25: float = int.from_bytes(data[2:4], byteorder="little") / 10 - self.pm10: float = int.from_bytes(data[4:6], byteorder="little") / 10 - - -class ReportingModeReadResponse(ReadResponse): - """Reporting mode response.""" - - def __init__(self, data: bytes): - """Create a reporting mode response.""" - super().__init__(data, command_code=Command.SET_REPORTING_MODE) - self.operation_type = OperationType(self.data[1:2]) - self.state = ReportingMode(self.data[2:3]) - - -class DeviceIdResponse(ReadResponse): - """Device ID response.""" - - def __init__(self, data: bytes): - """Create a device ID response.""" - super().__init__(data, command_code=Command.SET_DEVICE_ID) - - -class SleepWakeReadResponse(ReadResponse): - """Sleep/Wake Response.""" - - def __init__(self, data: bytes): - """Create a sleep/wake response.""" - super().__init__(data, command_code=Command.SET_SLEEP) - self.operation_type = OperationType(self.data[1:2]) - self.state = SleepState(self.data[2:3]) - - -class WorkingPeriodReadResponse(ReadResponse): - """Working period response.""" - - def __init__(self, data: bytes): - """Create a working period response.""" - super().__init__(data, command_code=Command.SET_WORKING_PERIOD) - self.operation_type = OperationType(self.data[1:2]) - self.interval: int = self.data[2] - - -class CheckFirmwareReadResponse(ReadResponse): - """Firmware response.""" - - def __init__(self, data: bytes): - """Create a firmware response.""" - super().__init__(data, command_code=Command.CHECK_FIRMWARE_VERSION) - self.year = self.data[1] - self.month = self.data[2] - self.day = self.data[3] diff --git a/poetry.lock b/poetry.lock index 7586865..773ccdc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,16 +2,20 @@ [[package]] name = "aiosqlite" -version = "0.18.0" +version = "0.19.0" description = "asyncio bridge to the standard sqlite3 module" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "aiosqlite-0.18.0-py3-none-any.whl", hash = "sha256:c3511b841e3a2c5614900ba1d179f366826857586f78abd75e7cbeb88e75a557"}, - {file = "aiosqlite-0.18.0.tar.gz", hash = "sha256:faa843ef5fb08bafe9a9b3859012d3d9d6f77ce3637899de20606b7fc39aa213"}, + {file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"}, + {file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"}, ] +[package.extras] +dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] +docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"] + [[package]] name = "anyio" version = "3.6.2" @@ -35,37 +39,37 @@ trio = ["trio (>=0.16,<0.22)"] [[package]] name = "black" -version = "23.1.0" +version = "23.3.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, - {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, - {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, - {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, - {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, - {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, - {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, - {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, - {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, - {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, - {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, - {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, - {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, - {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, ] [package.dependencies] @@ -302,38 +306,38 @@ files = [ [[package]] name = "mypy" -version = "1.1.1" +version = "1.3.0" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af"}, - {file = "mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1"}, - {file = "mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799"}, - {file = "mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78"}, - {file = "mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e"}, - {file = "mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389"}, - {file = "mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2"}, - {file = "mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5"}, - {file = "mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c"}, - {file = "mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54"}, - {file = "mypy-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7c7b708fe9a871a96626d61912e3f4ddd365bf7f39128362bc50cbd74a634d5"}, - {file = "mypy-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c10fa12df1232c936830839e2e935d090fc9ee315744ac33b8a32216b93707"}, - {file = "mypy-1.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a28a76785bf57655a8ea5eb0540a15b0e781c807b5aa798bd463779988fa1d5"}, - {file = "mypy-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef6a01e563ec6a4940784c574d33f6ac1943864634517984471642908b30b6f7"}, - {file = "mypy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d64c28e03ce40d5303450f547e07418c64c241669ab20610f273c9e6290b4b0b"}, - {file = "mypy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64cc3afb3e9e71a79d06e3ed24bb508a6d66f782aff7e56f628bf35ba2e0ba51"}, - {file = "mypy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce61663faf7a8e5ec6f456857bfbcec2901fbdb3ad958b778403f63b9e606a1b"}, - {file = "mypy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b0c373d071593deefbcdd87ec8db91ea13bd8f1328d44947e88beae21e8d5e9"}, - {file = "mypy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2888ce4fe5aae5a673386fa232473014056967f3904f5abfcf6367b5af1f612a"}, - {file = "mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598"}, - {file = "mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c"}, - {file = "mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a"}, - {file = "mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f"}, - {file = "mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f"}, - {file = "mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4"}, - {file = "mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, + {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, + {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, + {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, + {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, + {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, + {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, + {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, + {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, + {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, + {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, + {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, + {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, + {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, + {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, + {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, + {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, + {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, + {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, + {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, + {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, + {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, + {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, ] [package.dependencies] @@ -361,14 +365,14 @@ files = [ [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] @@ -385,19 +389,19 @@ files = [ [[package]] name = "platformdirs" -version = "3.1.1" +version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, - {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, + {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, + {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -417,48 +421,48 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "1.10.5" +version = "1.10.7" description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb"}, - {file = "pydantic-1.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3bb99cf9655b377db1a9e47fa4479e3330ea96f4123c6c8200e482704bf1eda2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2185a3b3d98ab4506a3f6707569802d2d92c3a7ba3a9a35683a7709ea6c2aaa2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f582cac9d11c227c652d3ce8ee223d94eb06f4228b52a8adaafa9fa62e73d5c9"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c9e5b778b6842f135902e2d82624008c6a79710207e28e86966cd136c621bfee"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72ef3783be8cbdef6bca034606a5de3862be6b72415dc5cb1fb8ddbac110049a"}, - {file = "pydantic-1.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:45edea10b75d3da43cfda12f3792833a3fa70b6eee4db1ed6aed528cef17c74e"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63200cd8af1af2c07964546b7bc8f217e8bda9d0a2ef0ee0c797b36353914984"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:305d0376c516b0dfa1dbefeae8c21042b57b496892d721905a6ec6b79494a66d"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd326aff5d6c36f05735c7c9b3d5b0e933b4ca52ad0b6e4b38038d82703d35b"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bb0452d7b8516178c969d305d9630a3c9b8cf16fcf4713261c9ebd465af0d73"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9a9d9155e2a9f38b2eb9374c88f02fd4d6851ae17b65ee786a87d032f87008f8"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f836444b4c5ece128b23ec36a446c9ab7f9b0f7981d0d27e13a7c366ee163f8a"}, - {file = "pydantic-1.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:8481dca324e1c7b715ce091a698b181054d22072e848b6fc7895cd86f79b4449"}, - {file = "pydantic-1.10.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87f831e81ea0589cd18257f84386bf30154c5f4bed373b7b75e5cb0b5d53ea87"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ce1612e98c6326f10888df951a26ec1a577d8df49ddcaea87773bfbe23ba5cc"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58e41dd1e977531ac6073b11baac8c013f3cd8706a01d3dc74e86955be8b2c0c"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6a4b0aab29061262065bbdede617ef99cc5914d1bf0ddc8bcd8e3d7928d85bd6"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36e44a4de37b8aecffa81c081dbfe42c4d2bf9f6dff34d03dce157ec65eb0f15"}, - {file = "pydantic-1.10.5-cp37-cp37m-win_amd64.whl", hash = "sha256:261f357f0aecda005934e413dfd7aa4077004a174dafe414a8325e6098a8e419"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b429f7c457aebb7fbe7cd69c418d1cd7c6fdc4d3c8697f45af78b8d5a7955760"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:663d2dd78596c5fa3eb996bc3f34b8c2a592648ad10008f98d1348be7ae212fb"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51782fd81f09edcf265823c3bf43ff36d00db246eca39ee765ef58dc8421a642"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c428c0f64a86661fb4873495c4fac430ec7a7cef2b8c1c28f3d1a7277f9ea5ab"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:76c930ad0746c70f0368c4596020b736ab65b473c1f9b3872310a835d852eb19"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3257bd714de9db2102b742570a56bf7978e90441193acac109b1f500290f5718"}, - {file = "pydantic-1.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:f5bee6c523d13944a1fdc6f0525bc86dbbd94372f17b83fa6331aabacc8fd08e"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:532e97c35719f137ee5405bd3eeddc5c06eb91a032bc755a44e34a712420daf3"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca9075ab3de9e48b75fa8ccb897c34ccc1519177ad8841d99f7fd74cf43be5bf"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd46a0e6296346c477e59a954da57beaf9c538da37b9df482e50f836e4a7d4bb"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3353072625ea2a9a6c81ad01b91e5c07fa70deb06368c71307529abf70d23325"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3f9d9b2be177c3cb6027cd67fbf323586417868c06c3c85d0d101703136e6b31"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b473d00ccd5c2061fd896ac127b7755baad233f8d996ea288af14ae09f8e0d1e"}, - {file = "pydantic-1.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:5f3bc8f103b56a8c88021d481410874b1f13edf6e838da607dcb57ecff9b4594"}, - {file = "pydantic-1.10.5-py3-none-any.whl", hash = "sha256:7c5b94d598c90f2f46b3a983ffb46ab806a67099d118ae0da7ef21a2a4033b28"}, - {file = "pydantic-1.10.5.tar.gz", hash = "sha256:9e337ac83686645a46db0e825acceea8e02fca4062483f40e9ae178e8bd1103a"}, + {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, + {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, + {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, + {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, + {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, + {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, + {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, + {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, + {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, + {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, ] [package.dependencies] @@ -552,6 +556,21 @@ files = [ {file = "ruff-0.0.256.tar.gz", hash = "sha256:f9a96b34a4870ee8cf2f3779cd7854620d1788a83b52374771266cf800541bb7"}, ] +[[package]] +name = "sds011lib" +version = "0.2.1" +description = "SDS011 Library" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "sds011lib-0.2.1-py3-none-any.whl", hash = "sha256:ffe5d1b3da85e00c925ad78de0b9ce291d7d98c51715c30ea22d91fc56cbbcea"}, + {file = "sds011lib-0.2.1.tar.gz", hash = "sha256:3f08aab3feeac62d596ffe0820bc760efd5c699f5e673fcf977729751352f6cc"}, +] + +[package.dependencies] +pyserial = ">=3.5,<4.0" + [[package]] name = "sniffio" version = "1.3.0" @@ -566,53 +585,53 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.46" +version = "1.4.48" description = "Database Abstraction Library" category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-1.4.46-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7001f16a9a8e06488c3c7154827c48455d1c1507d7228d43e781afbc8ceccf6d"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c7a46639ba058d320c9f53a81db38119a74b8a7a1884df44d09fbe807d028aaf"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win32.whl", hash = "sha256:c04144a24103135ea0315d459431ac196fe96f55d3213bfd6d39d0247775c854"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win_amd64.whl", hash = "sha256:7b81b1030c42b003fc10ddd17825571603117f848814a344d305262d370e7c34"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:939f9a018d2ad04036746e15d119c0428b1e557470361aa798e6e7d7f5875be0"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b7f4b6aa6e87991ec7ce0e769689a977776db6704947e562102431474799a857"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf17ac9a61e7a3f1c7ca47237aac93cabd7f08ad92ac5b96d6f8dea4287fc1"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f8267682eb41a0584cf66d8a697fef64b53281d01c93a503e1344197f2e01fe"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cb0ad8a190bc22d2112001cfecdec45baffdf41871de777239da6a28ed74b6"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win32.whl", hash = "sha256:5f752676fc126edc1c4af0ec2e4d2adca48ddfae5de46bb40adbd3f903eb2120"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win_amd64.whl", hash = "sha256:31de1e2c45e67a5ec1ecca6ec26aefc299dd5151e355eb5199cd9516b57340be"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d68e1762997bfebf9e5cf2a9fd0bcf9ca2fdd8136ce7b24bbd3bbfa4328f3e4a"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d112b0f3c1bc5ff70554a97344625ef621c1bfe02a73c5d97cac91f8cd7a41e"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fac0a7054d86b997af12dc23f581cf0b25fb1c7d1fed43257dee3af32d3d6d"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win32.whl", hash = "sha256:887865924c3d6e9a473dc82b70977395301533b3030d0f020c38fd9eba5419f2"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win_amd64.whl", hash = "sha256:984ee13543a346324319a1fb72b698e521506f6f22dc37d7752a329e9cd00a32"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9167d4227b56591a4cc5524f1b79ccd7ea994f36e4c648ab42ca995d28ebbb96"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d61e9ecc849d8d44d7f80894ecff4abe347136e9d926560b818f6243409f3c86"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3ec187acf85984263299a3f15c34a6c0671f83565d86d10f43ace49881a82718"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9883f5fae4fd8e3f875adc2add69f8b945625811689a6c65866a35ee9c0aea23"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win32.whl", hash = "sha256:535377e9b10aff5a045e3d9ada8a62d02058b422c0504ebdcf07930599890eb0"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win_amd64.whl", hash = "sha256:18cafdb27834fa03569d29f571df7115812a0e59fd6a3a03ccb0d33678ec8420"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:a1ad90c97029cc3ab4ffd57443a20fac21d2ec3c89532b084b073b3feb5abff3"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4847f4b1d822754e35707db913396a29d874ee77b9c3c3ef3f04d5a9a6209618"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c5a99282848b6cae0056b85da17392a26b2d39178394fc25700bcf967e06e97a"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b1cc7835b39835c75cf7c20c926b42e97d074147c902a9ebb7cf2c840dc4e2"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win32.whl", hash = "sha256:c522e496f9b9b70296a7675272ec21937ccfc15da664b74b9f58d98a641ce1b6"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win_amd64.whl", hash = "sha256:ae067ab639fa499f67ded52f5bc8e084f045d10b5ac7bb928ae4ca2b6c0429a5"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e3c1808008124850115a3f7e793a975cfa5c8a26ceeeb9ff9cbb4485cac556df"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d164df3d83d204c69f840da30b292ac7dc54285096c6171245b8d7807185aa"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b33ffbdbbf5446cf36cd4cc530c9d9905d3c2fe56ed09e25c22c850cdb9fac92"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d94682732d1a0def5672471ba42a29ff5e21bb0aae0afa00bb10796fc1e28dd"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win32.whl", hash = "sha256:f8cb80fe8d14307e4124f6fad64dfd87ab749c9d275f82b8b4ec84c84ecebdbe"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win_amd64.whl", hash = "sha256:07e48cbcdda6b8bc7a59d6728bd3f5f574ffe03f2c9fb384239f3789c2d95c2e"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1b1e5e96e2789d89f023d080bee432e2fef64d95857969e70d3cadec80bd26f0"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3714e5b33226131ac0da60d18995a102a17dddd42368b7bdd206737297823ad"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:955162ad1a931fe416eded6bb144ba891ccbf9b2e49dc7ded39274dd9c5affc5"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6e4cb5c63f705c9d546a054c60d326cbde7421421e2d2565ce3e2eee4e1a01f"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win32.whl", hash = "sha256:51e1ba2884c6a2b8e19109dc08c71c49530006c1084156ecadfaadf5f9b8b053"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win_amd64.whl", hash = "sha256:315676344e3558f1f80d02535f410e80ea4e8fddba31ec78fe390eff5fb8f466"}, - {file = "SQLAlchemy-1.4.46.tar.gz", hash = "sha256:6913b8247d8a292ef8315162a51931e2b40ce91681f1b6f18f697045200c4a30"}, + {file = "SQLAlchemy-1.4.48-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:4bac3aa3c3d8bc7408097e6fe8bf983caa6e9491c5d2e2488cfcfd8106f13b6a"}, + {file = "SQLAlchemy-1.4.48-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbcae0e528d755f4522cad5842f0942e54b578d79f21a692c44d91352ea6d64e"}, + {file = "SQLAlchemy-1.4.48-cp27-cp27m-win32.whl", hash = "sha256:cbbe8b8bffb199b225d2fe3804421b7b43a0d49983f81dc654d0431d2f855543"}, + {file = "SQLAlchemy-1.4.48-cp27-cp27m-win_amd64.whl", hash = "sha256:627e04a5d54bd50628fc8734d5fc6df2a1aa5962f219c44aad50b00a6cdcf965"}, + {file = "SQLAlchemy-1.4.48-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9af1db7a287ef86e0f5cd990b38da6bd9328de739d17e8864f1817710da2d217"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:ce7915eecc9c14a93b73f4e1c9d779ca43e955b43ddf1e21df154184f39748e5"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5381ddd09a99638f429f4cbe1b71b025bed318f6a7b23e11d65f3eed5e181c33"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:87609f6d4e81a941a17e61a4c19fee57f795e96f834c4f0a30cee725fc3f81d9"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb0808ad34167f394fea21bd4587fc62f3bd81bba232a1e7fbdfa17e6cfa7cd7"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-win32.whl", hash = "sha256:d53cd8bc582da5c1c8c86b6acc4ef42e20985c57d0ebc906445989df566c5603"}, + {file = "SQLAlchemy-1.4.48-cp310-cp310-win_amd64.whl", hash = "sha256:4355e5915844afdc5cf22ec29fba1010166e35dd94a21305f49020022167556b"}, + {file = "SQLAlchemy-1.4.48-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:066c2b0413e8cb980e6d46bf9d35ca83be81c20af688fedaef01450b06e4aa5e"}, + {file = "SQLAlchemy-1.4.48-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c99bf13e07140601d111a7c6f1fc1519914dd4e5228315bbda255e08412f61a4"}, + {file = "SQLAlchemy-1.4.48-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee26276f12614d47cc07bc85490a70f559cba965fb178b1c45d46ffa8d73fda"}, + {file = "SQLAlchemy-1.4.48-cp311-cp311-win32.whl", hash = "sha256:49c312bcff4728bffc6fb5e5318b8020ed5c8b958a06800f91859fe9633ca20e"}, + {file = "SQLAlchemy-1.4.48-cp311-cp311-win_amd64.whl", hash = "sha256:cef2e2abc06eab187a533ec3e1067a71d7bbec69e582401afdf6d8cad4ba3515"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3509159e050bd6d24189ec7af373359f07aed690db91909c131e5068176c5a5d"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc2ab4d9f6d9218a5caa4121bdcf1125303482a1cdcfcdbd8567be8518969c0"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1ddbbcef9bcedaa370c03771ebec7e39e3944782bef49e69430383c376a250b"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f82d8efea1ca92b24f51d3aea1a82897ed2409868a0af04247c8c1e4fef5890"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-win32.whl", hash = "sha256:e3e98d4907805b07743b583a99ecc58bf8807ecb6985576d82d5e8ae103b5272"}, + {file = "SQLAlchemy-1.4.48-cp36-cp36m-win_amd64.whl", hash = "sha256:25887b4f716e085a1c5162f130b852f84e18d2633942c8ca40dfb8519367c14f"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:0817c181271b0ce5df1aa20949f0a9e2426830fed5ecdcc8db449618f12c2730"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1dd2562313dd9fe1778ed56739ad5d9aae10f9f43d9f4cf81d65b0c85168bb"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:68413aead943883b341b2b77acd7a7fe2377c34d82e64d1840860247cec7ff7c"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbde5642104ac6e95f96e8ad6d18d9382aa20672008cf26068fe36f3004491df"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-win32.whl", hash = "sha256:11c6b1de720f816c22d6ad3bbfa2f026f89c7b78a5c4ffafb220e0183956a92a"}, + {file = "SQLAlchemy-1.4.48-cp37-cp37m-win_amd64.whl", hash = "sha256:eb5464ee8d4bb6549d368b578e9529d3c43265007193597ddca71c1bae6174e6"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:92e6133cf337c42bfee03ca08c62ba0f2d9695618c8abc14a564f47503157be9"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d29a3fc6d9c45962476b470a81983dd8add6ad26fdbfae6d463b509d5adcda"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:005e942b451cad5285015481ae4e557ff4154dde327840ba91b9ac379be3b6ce"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8cfe951ed074ba5e708ed29c45397a95c4143255b0d022c7c8331a75ae61f3"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-win32.whl", hash = "sha256:2b9af65cc58726129d8414fc1a1a650dcdd594ba12e9c97909f1f57d48e393d3"}, + {file = "SQLAlchemy-1.4.48-cp38-cp38-win_amd64.whl", hash = "sha256:2b562e9d1e59be7833edf28b0968f156683d57cabd2137d8121806f38a9d58f4"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a1fc046756cf2a37d7277c93278566ddf8be135c6a58397b4c940abf837011f4"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d9b55252d2ca42a09bcd10a697fa041e696def9dfab0b78c0aaea1485551a08"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6dab89874e72a9ab5462997846d4c760cdb957958be27b03b49cf0de5e5c327c"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd8b5ee5a3acc4371f820934b36f8109ce604ee73cc668c724abb054cebcb6e"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-win32.whl", hash = "sha256:eee09350fd538e29cfe3a496ec6f148504d2da40dbf52adefb0d2f8e4d38ccc4"}, + {file = "SQLAlchemy-1.4.48-cp39-cp39-win_amd64.whl", hash = "sha256:7ad2b0f6520ed5038e795cc2852eb5c1f20fa6831d73301ced4aafbe3a10e1f6"}, + {file = "SQLAlchemy-1.4.48.tar.gz", hash = "sha256:b47bc287096d989a0838ce96f7d8e966914a24da877ed41a7531d44b55cdb8df"}, ] [package.dependencies] @@ -684,14 +703,14 @@ files = [ [[package]] name = "uvicorn" -version = "0.21.0" +version = "0.21.1" description = "The lightning-fast ASGI server." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "uvicorn-0.21.0-py3-none-any.whl", hash = "sha256:e69e955cb621ae7b75f5590a814a4fcbfb14cb8f44a36dfe3c5c75ab8aee3ad5"}, - {file = "uvicorn-0.21.0.tar.gz", hash = "sha256:8635a388062222082f4b06225b867b74a7e4ef942124453d4d1d1a5cb3750932"}, + {file = "uvicorn-0.21.1-py3-none-any.whl", hash = "sha256:e47cac98a6da10cd41e6fd036d472c6f58ede6c5dbee3dbee3ef7a100ed97742"}, + {file = "uvicorn-0.21.1.tar.gz", hash = "sha256:0fac9cb342ba099e0d582966005f3fdba5b0290579fed4a6266dc702ca7bb032"}, ] [package.dependencies] @@ -704,4 +723,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2c9ce04b15790c81f55f91f1c72b030030bdc58ffb2120f2fbd8b387162db80a" +content-hash = "067c950af04e8d38c5e6c124b431e9326752ed8f87144016e4f356bd7a239926" diff --git a/pyproject.toml b/pyproject.toml index d13a5f0..bb4d5f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ pyserial = "^3.5" uvicorn = "^0.21.0" databases = {extras = ["aiosqlite"], version = "^0.7.0"} fastapi-utils = "^0.2.1" +sds011lib = "^0.2.0" [tool.poetry.group.dev.dependencies] diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..b5a7ea2 Binary files /dev/null and b/screenshot.png differ diff --git a/tests/read/sds011/__init__.py b/tests/read/sds011/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/read/sds011/mock_device_serial.py b/tests/read/sds011/mock_device_serial.py deleted file mode 100644 index 111e247..0000000 --- a/tests/read/sds011/mock_device_serial.py +++ /dev/null @@ -1,145 +0,0 @@ -from serial import Serial -from aqimon.read.sds011.constants import ( - HEAD, - TAIL, - Command, - ReportingMode, - OperationType, - ResponseType, - SleepState, -) -from dataclasses import dataclass - - -@dataclass(frozen=True) -class WriteData: - """Simple wrapper for parsed write data.""" - - raw_data: bytes - raw_body_data: bytes - command: Command - - -class Sds011SerialEmulator(Serial): - """Emulated SDS011 Serial Port. - - Behaves like the device itself, except is a bit more predictable in ACTIVE mode. - """ - - def __init__(self) -> None: - """Create the emulator. - - Initializes to factory defaults. - """ - super().__init__() - self.response_buffer = b"" - self.operation_type = b"" - self.query_mode = ReportingMode.ACTIVE - self.device_id = b"\x01\x01" - self.sleep_state = SleepState.WORK - self.working_period = bytes([0]) - self.firmware_year = bytes([1]) - self.firmware_month = bytes([2]) - self.firmware_day = bytes([3]) - - def open(self): - """No-op open.""" - pass - - def close(self): - """No-op close.""" - pass - - def read(self, size: int = 1) -> bytes: - """Read from the emulator.""" - if self.query_mode == ReportingMode.ACTIVE and self.sleep_state == SleepState.WORK: - # If in active mode and awake, always return query response. - return self._get_query_response() - else: - response = self.response_buffer - self.response_buffer = b"" - return response - - def _generate_read(self, response_type: ResponseType, cmd: bytes) -> bytes: - """Generate a read command, with wrapper and checksum.""" - cmd_and_id = cmd + self.device_id - return HEAD + response_type.value + cmd_and_id + read_checksum(cmd_and_id) + TAIL - - def write(self, data: bytes) -> int: - """Write to the emulator.""" - last_write = parse_write_data(data) - self.operation_type = last_write.raw_body_data[1:2] - - if self.sleep_state == SleepState.SLEEP and last_write.command != Command.SET_SLEEP: - # Device ignores commands in sleep mode, unless its a sleep command - return len(data) - - if last_write.command == Command.SET_REPORTING_MODE: - if OperationType(last_write.raw_body_data[1:2]) == OperationType.SET_MODE: - self.query_mode = ReportingMode(last_write.raw_body_data[2:3]) - self._set_response_buffer(self._set_reporting_mode_response()) - elif last_write.command == Command.QUERY: - self._set_response_buffer(self._get_query_response()) - elif last_write.command == Command.SET_DEVICE_ID: - self.device_id = last_write.raw_body_data[11:13] - self._set_response_buffer(self._set_device_id_response()) - elif last_write.command == Command.SET_SLEEP: - if OperationType(last_write.raw_body_data[1:2]) == OperationType.SET_MODE: - self.sleep_state = SleepState(last_write.raw_body_data[2:3]) - self._set_response_buffer(self._set_sleep_response()) - elif last_write.command == Command.SET_WORKING_PERIOD: - if OperationType(last_write.raw_body_data[1:2]) == OperationType.SET_MODE: - self.working_period = last_write.raw_body_data[2:3] - self._set_response_buffer(self._set_working_period_response()) - elif last_write.command == Command.CHECK_FIRMWARE_VERSION: - self._set_response_buffer(self._check_firmware_response()) - return len(data) - - def _get_query_response(self) -> bytes: - return self._generate_read(ResponseType.QUERY_RESPONSE, b"\x19\x00\x64\x00") - - def _set_response_buffer(self, data: bytes) -> None: - # Response buffer should only be written if there wasn't something already there. - if self.response_buffer == b"": - self.response_buffer = data - - def _set_reporting_mode_response(self) -> bytes: - return self._generate_read( - ResponseType.GENERAL_RESPONSE, - Command.SET_REPORTING_MODE.value + self.operation_type + self.query_mode.value + b"\x00", - ) - - def _set_device_id_response(self) -> bytes: - return self._generate_read(ResponseType.GENERAL_RESPONSE, Command.SET_DEVICE_ID.value + (b"\x00" * 3)) - - def _set_sleep_response(self) -> bytes: - return self._generate_read( - ResponseType.GENERAL_RESPONSE, - Command.SET_SLEEP.value + self.operation_type + self.sleep_state.value + b"\x00", - ) - - def _set_working_period_response(self) -> bytes: - return self._generate_read( - ResponseType.GENERAL_RESPONSE, - Command.SET_WORKING_PERIOD.value + self.operation_type + self.working_period + b"\x00", - ) - - def _check_firmware_response(self) -> bytes: - return self._generate_read( - ResponseType.GENERAL_RESPONSE, - Command.CHECK_FIRMWARE_VERSION.value + self.firmware_year + self.firmware_month + self.firmware_day, - ) - - -def read_checksum(data: bytes) -> bytes: - """Generate a checksum for the data bytes of a command.""" - if len(data) != 6: - raise AttributeError("Invalid checksum length.") - return bytes([sum(d for d in data) % 256]) - - -def parse_write_data(data: bytes) -> WriteData: - """Parse write data from the emulator into a neater wrapper.""" - if len(data) != 19: - raise AttributeError("Data is wrong size.") - return WriteData(raw_data=data, raw_body_data=data[2:15], command=Command(data[2:3])) diff --git a/tests/read/sds011/test_sds011.py b/tests/read/sds011/test_sds011.py deleted file mode 100644 index 41da71f..0000000 --- a/tests/read/sds011/test_sds011.py +++ /dev/null @@ -1,349 +0,0 @@ -import pytest - -from aqimon.read.sds011 import NovaPmReader, ActiveModeReader, QueryModeReader -from aqimon.read.sds011.constants import ReportingMode, SleepState -from aqimon.read.sds011.exceptions import IncorrectCommandException, IncompleteReadException -from .mock_device_serial import Sds011SerialEmulator - - -class TestBaseReader: - @pytest.fixture - def reader(self): - # If you want to run these tests an integration you can replace the emulator here with a real serial device. - # ser_dev = serial.Serial('/dev/ttyUSB0', timeout=2, baudrate=9600) - # reader = NovaPmReader(ser_dev=ser_dev) - - ser_dev = Sds011SerialEmulator() - reader = NovaPmReader(ser_dev=ser_dev, send_command_sleep=0) - - # flush out the reader in case theres leftovers in the buffer - ser_dev.read(10) - - reader.wake() - # We don't know if the device was in active or querying. We must flush out the buffer from the above `wake`, - # if it exists. - ser_dev.read(10) - - reader.set_active_mode() - reader.set_working_period(0) - - yield reader - # Sleep the reader at the end so its not left on. - reader.sleep() - ser_dev.close() - - def test_hammer_reporting_mode(self, reader: NovaPmReader): - # Switch the modes - reader.set_query_mode() - reader.set_active_mode() - - # Set again in active mode - reader.set_active_mode() - - # set it in query mode twice - reader.set_query_mode() - reader.set_query_mode() - - reader.request_reporting_mode() - assert reader.query_reporting_mode().state == ReportingMode.QUERYING - - def test_hammer_sleep_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.sleep() - result = reader.query_sleep_state() - assert result.state == SleepState.SLEEP - - reader.wake() - reader.request_sleep_state() - assert reader.query_sleep_state().state == SleepState.WORK - reader.set_query_mode() - reader.request_sleep_state() - result = reader.query_sleep_state() - assert result.state == SleepState.WORK - - def test_hammer_sleep_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - reader.sleep() - - reader.wake() - reader.set_active_mode() - result = reader.query_data() - assert result.pm25 > 0.0 - - def test_queries_in_sleep_mode_are_incomplete(self, reader: NovaPmReader): - # Device can't be asked anything in sleep mode. - reader.set_query_mode() - reader.sleep() - assert reader.query_sleep_state().state == SleepState.SLEEP - - reader.request_reporting_mode() - with pytest.raises(IncompleteReadException): - reader.query_reporting_mode() - - reader.request_working_period() - with pytest.raises(IncompleteReadException): - reader.query_working_period() - - def test_values_changed_sleep_mode_arent_persisted(self, reader: NovaPmReader): - # Device can't set values while its asleep. - reader.set_query_mode() - reader.sleep() - assert reader.query_sleep_state().state == SleepState.SLEEP - - reader.set_working_period(20) - - reader.wake() - assert reader.query_sleep_state().state == SleepState.WORK - - reader.request_working_period() - assert reader.query_working_period().interval == 0 - - def test_buffer_is_first_command(self, reader: NovaPmReader): - reader.set_query_mode() - - # In query mode, once a command has been issued, subsequent commands should be effectively ignored, until the - # buffer is read. - reader.request_sleep_state() - reader.request_firmware_version() - reader.request_working_period() - - # We should still only get sleep state since its first - result = reader.query_sleep_state() - assert result.state == SleepState.WORK - - def test_get_reporting_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.request_reporting_mode() - result = reader.query_reporting_mode() - assert result.state == ReportingMode.QUERYING - - def test_get_reporting_mode_while_active_fails(self, reader: NovaPmReader): - reader.set_active_mode() - with pytest.raises(IncorrectCommandException): - reader.query_reporting_mode() - - def test_query_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - result = reader.query_data() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_query_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.request_data() - result = reader.query_data() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_set_device_id_query_mode(self, reader: NovaPmReader): - new_device_id = b"\xbb\xaa" - reader.set_query_mode() - reader.set_device_id(new_device_id) - result = reader.query_device_id() - assert result.device_id == new_device_id - - # Verify other commands also report correct ID - reader.request_reporting_mode() - result2 = reader.query_reporting_mode() - assert result2.device_id == new_device_id - - def test_sleep_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.sleep() - result = reader.query_sleep_state() - assert result.state == SleepState.SLEEP - - def test_sleep_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - reader.sleep() - - def test_wake_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.wake() - result = reader.query_sleep_state() - assert result.state == SleepState.WORK - - def test_wake_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - reader.wake() - - def test_get_sleep_state_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.wake() - result = reader.query_sleep_state() - assert result.state == SleepState.WORK - - def test_get_sleep_state_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - with pytest.raises(IncorrectCommandException): - reader.query_sleep_state() - - def test_set_working_period_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.set_working_period(10) - result = reader.query_working_period() - assert result.interval == 10 - - def test_set_working_period_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - reader.set_working_period(10) - - def test_get_working_period_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.set_working_period(10) - result = reader.query_working_period() - assert result.interval == 10 - - def test_get_working_period_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - with pytest.raises(IncorrectCommandException): - reader.query_working_period() - - def test_get_firmware_version_query_mode(self, reader: NovaPmReader): - reader.set_query_mode() - reader.request_firmware_version() - result = reader.query_firmware_version() - assert 99 >= result.year >= 0 - assert 12 >= result.month >= 1 - assert 31 >= result.day >= 1 - - def test_get_firmware_version_active_mode(self, reader: NovaPmReader): - reader.set_active_mode() - with pytest.raises(IncorrectCommandException): - reader.query_firmware_version() - - -class TestActiveModeReader: - @pytest.fixture - def reader(self): - # If you want to run these tests an integration you can replace the emulator here with a real serial device. - # ser_dev = serial.Serial("/dev/ttyUSB0", timeout=2, baudrate=9600) - # reader = ActiveModeReader(ser_dev=ser_dev, send_command_sleep=5) - - ser_dev = Sds011SerialEmulator() - reader = ActiveModeReader(ser_dev=ser_dev, send_command_sleep=0) - reader.set_working_period(0) - ser_dev.read(10) - - yield reader - # Sleep the reader at the end so its not left on. - reader.base_reader.sleep() - ser_dev.close() - - def test_query(self, reader: ActiveModeReader): - result = reader.query() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_query_sleep_mode(self, reader: ActiveModeReader): - reader.sleep() - - with pytest.raises(IncompleteReadException): - reader.query() - - def test_wake(self, reader: ActiveModeReader): - reader.sleep() - with pytest.raises(IncompleteReadException): - reader.query() - reader.wake() - - # Make sure we can read again. - result = reader.query() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_set_working_period(self, reader: ActiveModeReader): - reader.set_working_period(20) - - # We can't really do much here to validate that this is working. Just ensure that we can still query after. - result = reader.query() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_set_device_id(self, reader: ActiveModeReader): - reader.set_device_id(b"\x12\x23") - - # We can't really do much here to validate that this is working. Just ensure that we can still query after. - result = reader.query() - assert result.device_id == b"\x12\x23" - - -class TestQueryModeReader: - @pytest.fixture - def reader(self): - # If you want to run these tests an integration you can replace the emulator here with a real serial device. - # ser_dev = serial.Serial("/dev/ttyUSB0", timeout=2, baudrate=9600) - # reader = QueryModeReader(ser_dev=ser_dev) - - ser_dev = Sds011SerialEmulator() - reader = QueryModeReader(ser_dev=ser_dev, send_command_sleep=0) - reader.set_working_period(0) - - yield reader - # Sleep the reader at the end so its not left on. - reader.base_reader.sleep() - ser_dev.close() - - def test_query(self, reader: QueryModeReader): - result = reader.query() - assert 999 > result.pm25 > 0 - assert 999 > result.pm10 > 0 - - def test_query_sleep_mode(self, reader: QueryModeReader): - reader.sleep() - - with pytest.raises(IncompleteReadException): - reader.query() - - def test_wake(self, reader: QueryModeReader): - reader.sleep() - with pytest.raises(IncompleteReadException): - reader.query() - result = reader.wake() - assert result.state == SleepState.WORK - - result2 = reader.query() - assert 999 > result2.pm25 > 0 - assert 999 > result2.pm10 > 0 - - def test_get_sleep_state(self, reader: QueryModeReader): - result = reader.sleep() - assert result.state == SleepState.SLEEP - - result = reader.wake() - assert result.state == SleepState.WORK - - result = reader.get_sleep_state() - assert result.state == SleepState.WORK - - # Make sure we can read again. - result2 = reader.query() - assert 999 > result2.pm25 > 0 - assert 999 > result2.pm10 > 0 - - def test_get_reporting_mode(self, reader: QueryModeReader): - result = reader.get_reporting_mode() - assert result.state == ReportingMode.QUERYING - - def test_set_working_period(self, reader: QueryModeReader): - result = reader.set_working_period(20) - assert result.interval == 20 - - def test_get_working_period(self, reader: QueryModeReader): - reader.set_working_period(20) - result = reader.get_working_period() - assert result.interval == 20 - - def test_set_device_id(self, reader: QueryModeReader): - result = reader.set_device_id(b"\x12\x23") - assert result.device_id == b"\x12\x23" - - # We can't really do much here to validate that this is working. Just ensure that we can still query after. - result2 = reader.query() - assert result2.device_id == b"\x12\x23" - - def test_get_firmware_version(self, reader: QueryModeReader): - result = reader.get_firmware_version() - assert 99 >= result.year >= 0 - assert 12 >= result.month >= 1 - assert 31 >= result.day >= 1