From e1075f0df5296def41a1a34a2d55894e0175a256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 22 Jan 2024 13:17:25 +0100 Subject: [PATCH 01/24] Introduce AirthingsDeviceType --- airthings_ble/const.py | 7 ------- airthings_ble/device_type.py | 22 ++++++++++++++++++++++ airthings_ble/parser.py | 30 ++++++++++++++++++------------ 3 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 airthings_ble/device_type.py diff --git a/airthings_ble/const.py b/airthings_ble/const.py index 0b6a3dd..c6af446 100644 --- a/airthings_ble/const.py +++ b/airthings_ble/const.py @@ -51,10 +51,3 @@ HUMIDITY_MAX = 100 RADON_MAX = 16383 TEMPERATURE_MAX = 100 - -DEVICE_TYPE = { - "2900": "Wave gen. 1", - "2920": "Wave Mini", - "2930": "Wave Plus", - "2950": "Wave Radon", -} diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py new file mode 100644 index 0000000..46df9d5 --- /dev/null +++ b/airthings_ble/device_type.py @@ -0,0 +1,22 @@ +from enum import Enum + + +class AirthingsDeviceType(Enum): + WAVE_GEN_1 = "2900" + WAVE_MINI = "2920" + WAVE_PLUS = "2930" + WAVE_RADON = "2950" + + @classmethod + def product_name(self): + if self == AirthingsDeviceType.WAVE_GEN_1: + return "Wave Gen 1" + elif self == AirthingsDeviceType.WAVE_MINI: + return "Wave Mini" + elif self == AirthingsDeviceType.WAVE_PLUS: + return "Wave Plus" + elif self == AirthingsDeviceType.WAVE_RADON: + return "Wave Radon" + else: + return "Unknown" + diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 5f95d4c..e697093 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -38,7 +38,6 @@ CHAR_UUID_WAVEMINI_DATA, CO2_MAX, COMMAND_UUID, - DEVICE_TYPE, HIGH, HUMIDITY_MAX, LOW, @@ -49,6 +48,7 @@ VERY_LOW, VOC_MAX, ) +from .device_type import AirthingsDeviceType if sys.version_info[:2] < (3, 11): from async_timeout import timeout as asyncio_timeout @@ -278,8 +278,8 @@ def make_data_receiver(self) -> _NotificationReceiver: class _NotificationReceiver: """Receiver for a single notification message. - A notification message that is larger than the MTU can get sent over multiple packets. This - receiver knows how to reconstruct it. + A notification message that is larger than the MTU can get sent over multiple + packets. This receiver knows how to reconstruct it. """ message: bytearray | None @@ -390,8 +390,7 @@ class AirthingsDeviceInfo: manufacturer: str = "" hw_version: str = "" sw_version: str = "" - model: Optional[str] = None - model_raw: str = "" + model: Optional[AirthingsDeviceType] = None name: str = "" identifier: str = "" address: str = "" @@ -450,17 +449,20 @@ async def _get_device_characteristics( self.logger.debug("Get device characteristics exception: %s", err) return - device_info.model_raw = data.decode("utf-8") - device_info.model = DEVICE_TYPE.get(device_info.model_raw) - if device_info.model is None: + try: + device_info.model = AirthingsDeviceType(data.decode("utf-8")) + except ValueError: + device_info.model = None + device_info.model_raw = data.decode("utf-8") self.logger.warning( "Could not map model number to model name, " "most likely an unsupported device: %s", - device_info.model_raw, + device_info.model, ) - model_raw = device_info.model_raw - characteristics = _CHARS_BY_MODELS.get(model_raw, device_info_characteristics) + characteristics = _CHARS_BY_MODELS.get( + device_info.model.value, device_info_characteristics + ) for characteristic in characteristics: if did_first_sync and characteristic.name != "firmware_rev": # Only the sw_version can change once set, so we can skip the rest. @@ -489,7 +491,11 @@ async def _get_device_characteristics( "Characteristics not handled: %s", characteristic.uuid ) - if model_raw == "2900" and device_info.name and not device_info.identifier: + if ( + device_info.model.value == AirthingsDeviceType.WAVE_GEN_1 + and device_info.name + and not device_info.identifier + ): # For the Wave gen. 1 we need to fetch the identifier in the device name. # Example: From `AT#123456-2900Radon` we need to fetch `123456`. wave1_identifier = re.search(r"(?<=\#)[0-9]{1,6}", device_info.name) From f8b81ef10df89a061ed07a93d6c00f406fae61f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 22 Jan 2024 13:17:35 +0100 Subject: [PATCH 02/24] Add battery calculation --- airthings_ble/device_type.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 46df9d5..f745d41 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -20,3 +20,36 @@ def product_name(self): else: return "Unknown" + @classmethod + def battery_percentage(self, vin) -> int: + if self == AirthingsDeviceType.WAVE_MINI: + # The only device that has a different battery voltage range (3 x AAA) + if vin >= 4.50: + return 100 + elif 4.20 <= vin < 4.50: + return int((vin - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) + elif 3.90 <= vin < 4.20: + return int((vin - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) + elif 3.75 <= vin < 3.90: + return int((vin - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) + elif 3.30 <= vin < 3.75: + return int((vin - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) + elif 2.40 <= vin < 3.30: + return int((vin - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) + return 0 + else: + # All other devices (2 x AA) + if vin >= 3.00: + return 100 + elif 2.80 <= vin < 3.00: + return int((vin - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) + elif 2.60 <= vin < 2.80: + return int((vin - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) + elif 2.50 <= vin < 2.60: + return int((vin - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) + elif 2.20 <= vin < 2.50: + return int((vin - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) + elif 2.10 <= vin < 2.20: + return int((vin - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) + else: + return 0 From d757a3cf02837fbec35b45238e3dc58417097572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 22 Jan 2024 13:28:00 +0100 Subject: [PATCH 03/24] Use new battery calculation --- airthings_ble/device_type.py | 2 +- airthings_ble/parser.py | 33 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index f745d41..a1f66cc 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -21,7 +21,7 @@ def product_name(self): return "Unknown" @classmethod - def battery_percentage(self, vin) -> int: + def battery_percentage(self, vin: float) -> int: if self == AirthingsDeviceType.WAVE_MINI: # The only device that has a different battery voltage range (3 x AAA) if vin >= 4.50: diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index e697093..74df5c4 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -335,7 +335,10 @@ def get_absolute_pressure(elevation: int, data: float) -> float: return data + round(offset, 2) -sensor_decoders: dict[str, Callable[[bytearray], dict[str, float | None | str]],] = { +sensor_decoders: dict[ + str, + Callable[[bytearray], dict[str, float | None | str]], +] = { str(CHAR_UUID_WAVE_PLUS_DATA): __decode_wave_plus( name="Plus", format_type="BBBBHHHHHHHH", scale=0 ), @@ -426,12 +429,10 @@ def __init__( logger: Logger, elevation: int | None = None, is_metric: bool = True, - voltage: tuple[float, float] = (2.4, 3.2), ) -> None: self.logger = logger self.is_metric = is_metric self.elevation = elevation - self.voltage = voltage self.device_info = AirthingsDeviceInfo() async def _get_device_characteristics( @@ -483,7 +484,8 @@ async def _get_device_characteristics( device_info.name = data.decode(characteristic.format) elif characteristic.name == "serial_nr": identifier = data.decode(characteristic.format) - # Some devices return `Serial Number` on Mac instead of the actual serial number. + # Some devices return `Serial Number` on Mac instead of + # the actual serial number. if identifier != "Serial Number": device_info.identifier = identifier else: @@ -515,7 +517,10 @@ async def _get_device_characteristics( setattr(device, name, getattr(device_info, name)) async def _get_service_characteristics( - self, client: BleakClient, device: AirthingsDevice + self, + client: BleakClient, + device: AirthingsDevice, + model: Optional[AirthingsDeviceType], ) -> None: svcs = client.services sensors = device.sensors @@ -585,21 +590,11 @@ async def _get_service_characteristics( ) if command_sensor_data is not None: # calculate battery percentage - v_min, v_max = self.voltage bat_pct: int | None = None - bat_data = command_sensor_data.get("battery") - if bat_data is not None: - bat = float(bat_data) - # set as tuple during lint somehow.. - bat_pct = ( - max( - 0, - min( - 100, - round((bat - v_min) / (v_max - v_min) * 100), - ), - ), - )[0] + + if (bat_data := command_sensor_data.get("battery")) is not None: + model.battery_percentage(float(bat_data)) + bat_pct = float(bat_data) sensors.update( { From 10738c1770f98167a4588813cc4ac3266579f93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 22 Jan 2024 23:03:55 +0100 Subject: [PATCH 04/24] Fix battery percentage and product name --- airthings_ble/device_type.py | 2 -- airthings_ble/parser.py | 14 ++++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index a1f66cc..1b80db1 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -7,7 +7,6 @@ class AirthingsDeviceType(Enum): WAVE_PLUS = "2930" WAVE_RADON = "2950" - @classmethod def product_name(self): if self == AirthingsDeviceType.WAVE_GEN_1: return "Wave Gen 1" @@ -20,7 +19,6 @@ def product_name(self): else: return "Unknown" - @classmethod def battery_percentage(self, vin: float) -> int: if self == AirthingsDeviceType.WAVE_MINI: # The only device that has a different battery voltage range (3 x AAA) diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 74df5c4..5e7ece5 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -402,7 +402,7 @@ class AirthingsDeviceInfo: def friendly_name(self) -> str: """Generate a name for the device.""" - return f"Airthings {self.model}" + return f"Airthings {self.model.product_name()}" @dataclasses.dataclass @@ -416,7 +416,7 @@ class AirthingsDevice(AirthingsDeviceInfo): def friendly_name(self) -> str: """Generate a name for the device.""" - return f"Airthings {self.model}" + return f"Airthings {self.model.product_name()}" # pylint: disable=too-many-locals @@ -451,6 +451,7 @@ async def _get_device_characteristics( return try: + self.logger.debug("Model number: %s", data.decode("utf-8")) device_info.model = AirthingsDeviceType(data.decode("utf-8")) except ValueError: device_info.model = None @@ -517,10 +518,7 @@ async def _get_device_characteristics( setattr(device, name, getattr(device_info, name)) async def _get_service_characteristics( - self, - client: BleakClient, - device: AirthingsDevice, - model: Optional[AirthingsDeviceType], + self, client: BleakClient, device: AirthingsDevice ) -> None: svcs = client.services sensors = device.sensors @@ -593,8 +591,8 @@ async def _get_service_characteristics( bat_pct: int | None = None if (bat_data := command_sensor_data.get("battery")) is not None: - model.battery_percentage(float(bat_data)) - bat_pct = float(bat_data) + self.logger.debug("Battery voltage: %s", bat_data) + bat_pct = device.model.battery_percentage(float(bat_data)) sensors.update( { From 5f88365d2e838adc0e2616010d696b4dd6b82b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Tue, 23 Jan 2024 02:37:01 +0100 Subject: [PATCH 05/24] Fix battery for Wave Mini and Wave Radon --- airthings_ble/const.py | 7 ++++--- airthings_ble/parser.py | 34 ++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/airthings_ble/const.py b/airthings_ble/const.py index c6af446..95f42c2 100644 --- a/airthings_ble/const.py +++ b/airthings_ble/const.py @@ -23,9 +23,10 @@ CHAR_UUID_WAVE_PLUS_DATA = UUID("b42e2a68-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_2_DATA = UUID("b42e4dcc-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVEMINI_DATA = UUID("b42e3b98-ade7-11e4-89d3-123b93f75cba") -COMMAND_UUID = UUID( - "b42e2d06-ade7-11e4-89d3-123b93f75cba" -) # "Access Control Point" Characteristic + +COMMAND_UUID_WAVE_2 = UUID("b42e50d8-ade7-11e4-89d3-123b93f75cba") +COMMAND_UUID_WAVE_PLUS = UUID("b42e2d06-ade7-11e4-89d3-123b93f75cba") +COMMAND_UUID_WAVE_MINI = UUID("b42e3ef4-ade7-11e4-89d3-123b93f75cba") """ 0 - 49 Bq/m3 (0 - 1.3 pCi/L): diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 5e7ece5..89ff43e 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -37,7 +37,9 @@ CHAR_UUID_WAVE_PLUS_DATA, CHAR_UUID_WAVEMINI_DATA, CO2_MAX, - COMMAND_UUID, + COMMAND_UUID_WAVE_2, + COMMAND_UUID_WAVE_PLUS, + COMMAND_UUID_WAVE_MINI, HIGH, HUMIDITY_MAX, LOW, @@ -80,7 +82,9 @@ CHAR_UUID_WAVE_PLUS_DATA, CHAR_UUID_WAVE_2_DATA, CHAR_UUID_WAVEMINI_DATA, - COMMAND_UUID, + COMMAND_UUID_WAVE_2, + COMMAND_UUID_WAVE_PLUS, + COMMAND_UUID_WAVE_MINI, ] sensors_characteristics_uuid_str = [str(x) for x in sensors_characteristics_uuid] @@ -233,9 +237,8 @@ class CommandDecode: cmd: bytes | bytearray - def __init__(self, name: str, format_type: str, cmd: bytes): + def __init__(self, format_type: str, cmd: bytes): """Initialize command decoder""" - self.name = name self.format_type = format_type self.cmd = cmd @@ -248,7 +251,7 @@ def decode_data( cmd = raw_data[0:1] if cmd != self.cmd: - logger.debug( + logger.warning( "Result for wrong command received, expected %s got %s", self.cmd.hex(), cmd.hex(), @@ -256,10 +259,11 @@ def decode_data( return None if len(raw_data[2:]) != struct.calcsize(self.format_type): - logger.debug( + logger.warning( "Wrong length data received (%s) versus expected (%s)", - len(cmd), + len(raw_data[2:]), struct.calcsize(self.format_type), + raw_data.hex(), ) return None val = struct.unpack(self.format_type, raw_data[2:]) @@ -340,10 +344,10 @@ def get_absolute_pressure(elevation: int, data: float) -> float: Callable[[bytearray], dict[str, float | None | str]], ] = { str(CHAR_UUID_WAVE_PLUS_DATA): __decode_wave_plus( - name="Plus", format_type="BBBBHHHHHHHH", scale=0 + name="Plus", format_type="4B8H", scale=0 ), str(CHAR_UUID_DATETIME): _decode_wave( - name="date_time", format_type="HBBBBB", scale=0 + name="date_time", format_type="H5B", scale=0 ), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", @@ -367,13 +371,19 @@ def get_absolute_pressure(elevation: int, data: float) -> float: name="Wave2", format_type="<4B8H", scale=1.0 ), str(CHAR_UUID_WAVEMINI_DATA): _decode_wave_mini( - name="WaveMini", format_type=" Date: Tue, 23 Jan 2024 02:44:32 +0100 Subject: [PATCH 06/24] Remove illuminance --- airthings_ble/const.py | 1 - airthings_ble/parser.py | 31 +++++-------------------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/airthings_ble/const.py b/airthings_ble/const.py index 95f42c2..d9e9dc2 100644 --- a/airthings_ble/const.py +++ b/airthings_ble/const.py @@ -19,7 +19,6 @@ CHAR_UUID_HUMIDITY = UUID("00002a6f-0000-1000-8000-00805f9b34fb") CHAR_UUID_RADON_1DAYAVG = UUID("b42e01aa-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_RADON_LONG_TERM_AVG = UUID("b42e0a4c-ade7-11e4-89d3-123b93f75cba") -CHAR_UUID_ILLUMINANCE_ACCELEROMETER = UUID("b42e1348-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_PLUS_DATA = UUID("b42e2a68-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_2_DATA = UUID("b42e4dcc-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVEMINI_DATA = UUID("b42e3b98-ade7-11e4-89d3-123b93f75cba") diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 89ff43e..10140e6 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -26,7 +26,6 @@ CHAR_UUID_FIRMWARE_REV, CHAR_UUID_HARDWARE_REV, CHAR_UUID_HUMIDITY, - CHAR_UUID_ILLUMINANCE_ACCELEROMETER, CHAR_UUID_MANUFACTURER_NAME, CHAR_UUID_MODEL_NUMBER_STRING, CHAR_UUID_RADON_1DAYAVG, @@ -78,7 +77,6 @@ CHAR_UUID_HUMIDITY, CHAR_UUID_RADON_1DAYAVG, CHAR_UUID_RADON_LONG_TERM_AVG, - CHAR_UUID_ILLUMINANCE_ACCELEROMETER, CHAR_UUID_WAVE_PLUS_DATA, CHAR_UUID_WAVE_2_DATA, CHAR_UUID_WAVEMINI_DATA, @@ -177,10 +175,11 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: data: dict[str, float | None | str] = {} data["date_time"] = str(datetime.isoformat(datetime.now())) data["temperature"] = validate_value( - value=round(val[1] / 100.0 - 273.15, 2), max_value=TEMPERATURE_MAX + value=round(val[2] / 100.0 - 273.15, 2), max_value=TEMPERATURE_MAX ) - data["humidity"] = validate_value(value=val[3] / 100.0, max_value=HUMIDITY_MAX) - data["voc"] = validate_value(value=val[4] * 1.0, max_value=VOC_MAX) + data["rel_atm_pressure"] = val[3] / 50.0 + data["humidity"] = validate_value(value=val[4] / 100.0, max_value=HUMIDITY_MAX) + data["voc"] = validate_value(value=val[5] * 1.0, max_value=VOC_MAX) return data return handler @@ -209,20 +208,6 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: return handler -def _decode_wave_illum_accel( - name: str, format_type: str, scale: float -) -> Callable[[bytearray], dict[str, float | None | str]]: - def handler(raw_data: bytearray) -> dict[str, float | None | str]: - vals = _decode_base(name, format_type, scale)(raw_data) - val = vals[name] - data: dict[str, float | None | str] = {} - data["illuminance"] = str(val[0] * scale) - data["accelerometer"] = str(val[1] * scale) - return data - - return handler - - def validate_value(value: float, max_value: float) -> Optional[float]: """Validate if the given 'value' is within the specified range [min, max]""" min_value = 0 @@ -245,7 +230,7 @@ def __init__(self, format_type: str, cmd: bytes): def decode_data( self, logger: Logger, raw_data: bytearray | None ) -> dict[str, float | str | None] | None: - """Decoder returns dict with illuminance and battery""" + """Decoder returns dict with battery""" if raw_data is None: return None @@ -268,8 +253,6 @@ def decode_data( return None val = struct.unpack(self.format_type, raw_data[2:]) res = {} - res["illuminance"] = val[2] - # res['measurement_periods'] = val[5] res["battery"] = val[17] / 1000.0 return res @@ -361,9 +344,6 @@ def get_absolute_pressure(elevation: int, data: float) -> float: str(CHAR_UUID_RADON_LONG_TERM_AVG): _decode_attr( name="radon_longterm_avg", format_type="H", scale=1.0 ), - str(CHAR_UUID_ILLUMINANCE_ACCELEROMETER): _decode_wave_illum_accel( - name="illuminance_accelerometer", format_type="BB", scale=1.0 - ), str(CHAR_UUID_TEMPERATURE): _decode_attr( name="temperature", format_type="h", scale=1.0 / 100.0 ), @@ -606,7 +586,6 @@ async def _get_service_characteristics( sensors.update( { - "illuminance": command_sensor_data.get("illuminance"), "battery": bat_pct, } ) From 91b583cb23cbb1c254fab73db453e1f9ef08d10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 00:33:11 +0100 Subject: [PATCH 07/24] Fix decoders, cleanup --- airthings_ble/const.py | 3 +- airthings_ble/parser.py | 184 ++++++++++++++++++++++++++++++---------- 2 files changed, 143 insertions(+), 44 deletions(-) diff --git a/airthings_ble/const.py b/airthings_ble/const.py index d9e9dc2..56fc5a1 100644 --- a/airthings_ble/const.py +++ b/airthings_ble/const.py @@ -21,6 +21,7 @@ CHAR_UUID_RADON_LONG_TERM_AVG = UUID("b42e0a4c-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_PLUS_DATA = UUID("b42e2a68-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_2_DATA = UUID("b42e4dcc-ade7-11e4-89d3-123b93f75cba") +CHAR_UUID_ILLUMINANCE_ACCELEROMETER = UUID("b42e1348-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVEMINI_DATA = UUID("b42e3b98-ade7-11e4-89d3-123b93f75cba") COMMAND_UUID_WAVE_2 = UUID("b42e50d8-ade7-11e4-89d3-123b93f75cba") @@ -48,6 +49,6 @@ CO2_MAX = 65534 VOC_MAX = 65534 -HUMIDITY_MAX = 100 +PERCENTAGE_MAX = 100 RADON_MAX = 16383 TEMPERATURE_MAX = 100 diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 10140e6..90676db 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -26,6 +26,7 @@ CHAR_UUID_FIRMWARE_REV, CHAR_UUID_HARDWARE_REV, CHAR_UUID_HUMIDITY, + CHAR_UUID_ILLUMINANCE_ACCELEROMETER, CHAR_UUID_MANUFACTURER_NAME, CHAR_UUID_MODEL_NUMBER_STRING, CHAR_UUID_RADON_1DAYAVG, @@ -40,7 +41,7 @@ COMMAND_UUID_WAVE_PLUS, COMMAND_UUID_WAVE_MINI, HIGH, - HUMIDITY_MAX, + PERCENTAGE_MAX, LOW, MODERATE, RADON_MAX, @@ -77,6 +78,7 @@ CHAR_UUID_HUMIDITY, CHAR_UUID_RADON_1DAYAVG, CHAR_UUID_RADON_LONG_TERM_AVG, + CHAR_UUID_ILLUMINANCE_ACCELEROMETER, CHAR_UUID_WAVE_PLUS_DATA, CHAR_UUID_WAVE_2_DATA, CHAR_UUID_WAVEMINI_DATA, @@ -125,7 +127,7 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: return handler -def __decode_wave_plus( +def _decode_wave_plus( name: str, format_type: str, scale: float ) -> Callable[[bytearray], dict[str, float | None | str]]: def handler(raw_data: bytearray) -> dict[str, float | None | str]: @@ -133,13 +135,16 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: val = vals[name] data: dict[str, float | None | str] = {} data["date_time"] = str(datetime.isoformat(datetime.now())) - data["humidity"] = validate_value(value=val[1] / 2.0, max_value=HUMIDITY_MAX) + data["humidity"] = validate_value(value=val[1] / 2.0, max_value=PERCENTAGE_MAX) + data["illuminance_pct"] = validate_value( + value=val[2] * 1.0, max_value=PERCENTAGE_MAX + ) data["radon_1day_avg"] = validate_value(value=val[4], max_value=RADON_MAX) data["radon_longterm_avg"] = validate_value(value=val[5], max_value=RADON_MAX) data["temperature"] = validate_value( value=val[6] / 100.0, max_value=TEMPERATURE_MAX ) - data["rel_atm_pressure"] = val[7] / 50.0 + data["rel_atm_pressure"] = float(val[7] / 50.0) data["co2"] = validate_value(value=val[8] * 1.0, max_value=CO2_MAX) data["voc"] = validate_value(value=val[9] * 1.0, max_value=VOC_MAX) return data @@ -147,7 +152,7 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: return handler -def __decode_wave_2( +def _decode_wave_2( name: str, format_type: str, scale: float ) -> Callable[[bytearray], dict[str, float | None | str]]: def handler(raw_data: bytearray) -> dict[str, float | None | str]: @@ -155,7 +160,10 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: val = vals[name] data: dict[str, float | None | str] = {} data["date_time"] = str(datetime.isoformat(datetime.now())) - data["humidity"] = validate_value(value=val[1] / 2.0, max_value=HUMIDITY_MAX) + data["illuminance_pct"] = validate_value( + value=val[2] * 1.0, max_value=PERCENTAGE_MAX + ) + data["humidity"] = validate_value(value=val[1] / 2.0, max_value=PERCENTAGE_MAX) data["radon_1day_avg"] = validate_value(value=val[4], max_value=RADON_MAX) data["radon_longterm_avg"] = validate_value(value=val[5], max_value=RADON_MAX) data["temperature"] = validate_value( @@ -177,8 +185,10 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: data["temperature"] = validate_value( value=round(val[2] / 100.0 - 273.15, 2), max_value=TEMPERATURE_MAX ) - data["rel_atm_pressure"] = val[3] / 50.0 - data["humidity"] = validate_value(value=val[4] / 100.0, max_value=HUMIDITY_MAX) + data["rel_atm_pressure"] = float(val[3] / 50.0) + data["humidity"] = validate_value( + value=val[4] / 100.0, max_value=PERCENTAGE_MAX + ) data["voc"] = validate_value(value=val[5] * 1.0, max_value=VOC_MAX) return data @@ -208,6 +218,21 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: return handler +def _decode_wave_illum_accel( + name: str, format_type: str, scale: float +) -> Callable[[bytearray], dict[str, float | None | str]]: + def handler(raw_data: bytearray) -> dict[str, float | None | str]: + vals = _decode_base(name, format_type, scale)(raw_data) + val = vals[name] + data: dict[str, float | None | str] = {} + # data["illuminance"] = validate_value(val[0] * scale, max_value=PERCENTAGE_MAX) + data["illuminance"] = val[0] * scale + data["accelerometer"] = str(val[1] * scale) + return data + + return handler + + def validate_value(value: float, max_value: float) -> Optional[float]: """Validate if the given 'value' is within the specified range [min, max]""" min_value = 0 @@ -216,22 +241,22 @@ def validate_value(value: float, max_value: float) -> Optional[float]: return None -# pylint: disable=too-few-public-methods class CommandDecode: """Decoder for the command response""" - cmd: bytes | bytearray - - def __init__(self, format_type: str, cmd: bytes): - """Initialize command decoder""" - self.format_type = format_type - self.cmd = cmd + cmd: bytes = b'\x6D' + format_type: str def decode_data( self, logger: Logger, raw_data: bytearray | None ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" + logger.debug("Command decoder not implemented, pass") + pass + + def validate_data(self, logger: Logger, raw_data: bytearray | None) -> None: if raw_data is None: + logger.debug("Validate data: No data received") return None cmd = raw_data[0:1] @@ -243,25 +268,92 @@ def decode_data( ) return None + logger.debug( + "Result for command received, %s: %s", + cmd.hex(), + raw_data.hex(), + ) + if len(raw_data[2:]) != struct.calcsize(self.format_type): logger.warning( "Wrong length data received (%s) versus expected (%s)", len(raw_data[2:]), struct.calcsize(self.format_type), - raw_data.hex(), ) return None - val = struct.unpack(self.format_type, raw_data[2:]) - res = {} - res["battery"] = val[17] / 1000.0 - - return res + + return struct.unpack(self.format_type, raw_data[2:]) def make_data_receiver(self) -> _NotificationReceiver: """Creates a notification receiver for the command.""" return _NotificationReceiver(struct.calcsize(self.format_type)) +class WavePlusCommandDecode(CommandDecode): + """Decoder for the Wave Plus command response""" + + def __init__(self): + """Initialize command decoder""" + self.format_type = " dict[str, float | str | None] | None: + """Decoder returns dict with battery""" + + if val := self.validate_data(logger, raw_data): + res = {} + res["battery"] = val[13] / 1000.0 + logger.debug("Battery voltage: %s", res["battery"]) + return res + + return None + + +class WaveRadonCommandDecode(CommandDecode): + """Decoder for the Wave Radon command response""" + + def __init__(self): + """Initialize command decoder""" + self.format_type = " dict[str, float | str | None] | None: + """Decoder returns dict with battery""" + + if val := self.validate_data(logger, raw_data): + res = {} + res["battery"] = val[13] / 1000.0 + logger.debug("Battery voltage: %s", res["battery"]) + + return res + + return None + + +class WaveMiniCommandDecode(CommandDecode): + """Decoder for the Wave Radon command response""" + + def __init__(self): + """Initialize command decoder""" + self.format_type = "<2L4B2HL4HL" + + def decode_data( + self, logger: Logger, raw_data: bytearray | None + ) -> dict[str, float | str | None] | None: + """Decoder returns dict with battery""" + + if val := self.validate_data(logger, raw_data): + res = {} + res["battery"] = val[11] / 1000.0 + logger.debug("Battery voltage: %s", res["battery"]) + + return res + + return None + + class _NotificationReceiver: """Receiver for a single notification message. @@ -326,9 +418,6 @@ def get_absolute_pressure(elevation: int, data: float) -> float: str, Callable[[bytearray], dict[str, float | None | str]], ] = { - str(CHAR_UUID_WAVE_PLUS_DATA): __decode_wave_plus( - name="Plus", format_type="4B8H", scale=0 - ), str(CHAR_UUID_DATETIME): _decode_wave( name="date_time", format_type="H5B", scale=0 ), @@ -336,7 +425,7 @@ def get_absolute_pressure(elevation: int, data: float) -> float: name="humidity", format_type="H", scale=1.0 / 100.0, - max_value=HUMIDITY_MAX, + max_value=PERCENTAGE_MAX, ), str(CHAR_UUID_RADON_1DAYAVG): _decode_attr( name="radon_1day_avg", format_type="H", scale=1.0 @@ -344,27 +433,27 @@ def get_absolute_pressure(elevation: int, data: float) -> float: str(CHAR_UUID_RADON_LONG_TERM_AVG): _decode_attr( name="radon_longterm_avg", format_type="H", scale=1.0 ), + str(CHAR_UUID_ILLUMINANCE_ACCELEROMETER): _decode_wave_illum_accel( + name="illuminance_accelerometer", format_type="BB", scale=1.0 + ), str(CHAR_UUID_TEMPERATURE): _decode_attr( name="temperature", format_type="h", scale=1.0 / 100.0 ), - str(CHAR_UUID_WAVE_2_DATA): __decode_wave_2( + str(CHAR_UUID_WAVE_2_DATA): _decode_wave_2( name="Wave2", format_type="<4B8H", scale=1.0 ), + str(CHAR_UUID_WAVE_PLUS_DATA): _decode_wave_plus( + name="Plus", format_type="<4B8H", scale=0 + ), str(CHAR_UUID_WAVEMINI_DATA): _decode_wave_mini( name="WaveMini", format_type="<2B5HLL", scale=1.0 ), } command_decoders: dict[str, CommandDecode] = { - str(COMMAND_UUID_WAVE_2): CommandDecode( - format_type=" Date: Mon, 29 Jan 2024 00:35:06 +0100 Subject: [PATCH 08/24] Fix linting --- airthings_ble/const.py | 2 +- airthings_ble/parser.py | 12 +++++------- tests/test_validate_value.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/airthings_ble/const.py b/airthings_ble/const.py index 56fc5a1..313504e 100644 --- a/airthings_ble/const.py +++ b/airthings_ble/const.py @@ -19,9 +19,9 @@ CHAR_UUID_HUMIDITY = UUID("00002a6f-0000-1000-8000-00805f9b34fb") CHAR_UUID_RADON_1DAYAVG = UUID("b42e01aa-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_RADON_LONG_TERM_AVG = UUID("b42e0a4c-ade7-11e4-89d3-123b93f75cba") +CHAR_UUID_ILLUMINANCE_ACCELEROMETER = UUID("b42e1348-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_PLUS_DATA = UUID("b42e2a68-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVE_2_DATA = UUID("b42e4dcc-ade7-11e4-89d3-123b93f75cba") -CHAR_UUID_ILLUMINANCE_ACCELEROMETER = UUID("b42e1348-ade7-11e4-89d3-123b93f75cba") CHAR_UUID_WAVEMINI_DATA = UUID("b42e3b98-ade7-11e4-89d3-123b93f75cba") COMMAND_UUID_WAVE_2 = UUID("b42e50d8-ade7-11e4-89d3-123b93f75cba") diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 90676db..e5bcc86 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -38,12 +38,12 @@ CHAR_UUID_WAVEMINI_DATA, CO2_MAX, COMMAND_UUID_WAVE_2, - COMMAND_UUID_WAVE_PLUS, COMMAND_UUID_WAVE_MINI, + COMMAND_UUID_WAVE_PLUS, HIGH, - PERCENTAGE_MAX, LOW, MODERATE, + PERCENTAGE_MAX, RADON_MAX, TEMPERATURE_MAX, UPDATE_TIMEOUT, @@ -244,7 +244,7 @@ def validate_value(value: float, max_value: float) -> Optional[float]: class CommandDecode: """Decoder for the command response""" - cmd: bytes = b'\x6D' + cmd: bytes = b"\x6D" format_type: str def decode_data( @@ -281,7 +281,7 @@ def validate_data(self, logger: Logger, raw_data: bytearray | None) -> None: struct.calcsize(self.format_type), ) return None - + return struct.unpack(self.format_type, raw_data[2:]) def make_data_receiver(self) -> _NotificationReceiver: @@ -418,9 +418,7 @@ def get_absolute_pressure(elevation: int, data: float) -> float: str, Callable[[bytearray], dict[str, float | None | str]], ] = { - str(CHAR_UUID_DATETIME): _decode_wave( - name="date_time", format_type="H5B", scale=0 - ), + str(CHAR_UUID_DATETIME): _decode_wave(name="date_time", format_type="H5B", scale=0), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", format_type="H", diff --git a/tests/test_validate_value.py b/tests/test_validate_value.py index e5b868a..5aceec3 100644 --- a/tests/test_validate_value.py +++ b/tests/test_validate_value.py @@ -1,7 +1,7 @@ import pytest -from airthings_ble.parser import validate_value from airthings_ble.const import CO2_MAX, HUMIDITY_MAX, RADON_MAX +from airthings_ble.parser import validate_value def test_validate_value_humidity(): From 71b4d440e1d21bfbdc1ee5f923d592789d02f405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 00:53:48 +0100 Subject: [PATCH 09/24] Fix existing test --- tests/test_validate_value.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_validate_value.py b/tests/test_validate_value.py index 5aceec3..0bae47d 100644 --- a/tests/test_validate_value.py +++ b/tests/test_validate_value.py @@ -1,17 +1,17 @@ import pytest -from airthings_ble.const import CO2_MAX, HUMIDITY_MAX, RADON_MAX +from airthings_ble.const import CO2_MAX, PERCENTAGE_MAX, RADON_MAX from airthings_ble.parser import validate_value def test_validate_value_humidity(): valid_humidity_values = [0, 50, 100.0] for value in valid_humidity_values: - assert validate_value(value=value, max_value=HUMIDITY_MAX) == value + assert validate_value(value=value, max_value=PERCENTAGE_MAX) == value invalid_humidity_values = [-1, 100.1, 101] for value in invalid_humidity_values: - assert validate_value(value=value, max_value=HUMIDITY_MAX) is None + assert validate_value(value=value, max_value=PERCENTAGE_MAX) is None def test_validate_value_radon(): From 201ce04204f1d0df843d4a4041525a27c81c7adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 00:53:53 +0100 Subject: [PATCH 10/24] Add tests --- tests/test_device_type.py | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_device_type.py diff --git a/tests/test_device_type.py b/tests/test_device_type.py new file mode 100644 index 0000000..b25fdd8 --- /dev/null +++ b/tests/test_device_type.py @@ -0,0 +1,62 @@ +import pytest + +from airthings_ble.device_type import AirthingsDeviceType + + +def test_device_type(): + """Test device type.""" + assert AirthingsDeviceType.WAVE_MINI.product_name() == "Wave Mini" + assert AirthingsDeviceType.WAVE_PLUS.product_name() == "Wave Plus" + assert AirthingsDeviceType.WAVE_RADON.product_name() == "Wave Radon" + assert AirthingsDeviceType.WAVE_GEN_1.product_name() == "Wave Gen 1" + assert AirthingsDeviceType("2900") == AirthingsDeviceType.WAVE_GEN_1 + assert AirthingsDeviceType("2920") == AirthingsDeviceType.WAVE_MINI + assert AirthingsDeviceType("2930") == AirthingsDeviceType.WAVE_PLUS + assert AirthingsDeviceType("2950") == AirthingsDeviceType.WAVE_RADON + with pytest.raises(ValueError): + AirthingsDeviceType("1234") + + +def test_battery_calculation(): + """Test battery calculation for all devices.""" + # Starting with the Wave Mini, since it has a different battery voltage range. + # Max voltage is 4.5V, min voltage is 2.4V. + + # Starting with 5V, which is more than the max voltage + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(5.0) == 100 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(4.5) == 100 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(4.2) == 85 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(3.9) == 62 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(3.75) == 42 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(3.3) == 23 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(2.4) == 0 + assert AirthingsDeviceType.WAVE_MINI.battery_percentage(2.3) == 0 + + # Repeat for the Wave Plus and Radon, which have the same battery voltage range. + # Max voltage is 3.0V, min voltage is 2.1V. + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(3.2) == 100 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(3.0) == 100 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.8) == 81 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.6) == 53 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.5) == 28 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.2) == 5 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.1) == 0 + assert AirthingsDeviceType.WAVE_PLUS.battery_percentage(2.0) == 0 + + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(3.2) == 100 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(3.0) == 100 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.8) == 81 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.6) == 53 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.5) == 28 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.2) == 5 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.1) == 0 + assert AirthingsDeviceType.WAVE_RADON.battery_percentage(2.0) == 0 + + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(3.2) == 100 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(3.0) == 100 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.8) == 81 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.6) == 53 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.5) == 28 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.2) == 5 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.1) == 0 + assert AirthingsDeviceType.WAVE_GEN_1.battery_percentage(2.0) == 0 From c61210bff487a073f71b79da31112ff740b2b21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 12:09:26 +0100 Subject: [PATCH 11/24] Add tests --- airthings_ble/parser.py | 32 ++--------- tests/test_absolute_pressure.py | 8 +++ tests/test_radon_level.py | 14 +++++ tests/test_wave_plus.py | 97 +++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 tests/test_absolute_pressure.py create mode 100644 tests/test_radon_level.py create mode 100644 tests/test_wave_plus.py diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index e5bcc86..dfc9273 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -152,7 +152,7 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: return handler -def _decode_wave_2( +def _decode_wave_radon( name: str, format_type: str, scale: float ) -> Callable[[bytearray], dict[str, float | None | str]]: def handler(raw_data: bytearray) -> dict[str, float | None | str]: @@ -289,7 +289,7 @@ def make_data_receiver(self) -> _NotificationReceiver: return _NotificationReceiver(struct.calcsize(self.format_type)) -class WavePlusCommandDecode(CommandDecode): +class WaveRadonAndPlusCommandDecode(CommandDecode): """Decoder for the Wave Plus command response""" def __init__(self): @@ -310,28 +310,6 @@ def decode_data( return None -class WaveRadonCommandDecode(CommandDecode): - """Decoder for the Wave Radon command response""" - - def __init__(self): - """Initialize command decoder""" - self.format_type = " dict[str, float | str | None] | None: - """Decoder returns dict with battery""" - - if val := self.validate_data(logger, raw_data): - res = {} - res["battery"] = val[13] / 1000.0 - logger.debug("Battery voltage: %s", res["battery"]) - - return res - - return None - - class WaveMiniCommandDecode(CommandDecode): """Decoder for the Wave Radon command response""" @@ -437,7 +415,7 @@ def get_absolute_pressure(elevation: int, data: float) -> float: str(CHAR_UUID_TEMPERATURE): _decode_attr( name="temperature", format_type="h", scale=1.0 / 100.0 ), - str(CHAR_UUID_WAVE_2_DATA): _decode_wave_2( + str(CHAR_UUID_WAVE_2_DATA): _decode_wave_radon( name="Wave2", format_type="<4B8H", scale=1.0 ), str(CHAR_UUID_WAVE_PLUS_DATA): _decode_wave_plus( @@ -449,8 +427,8 @@ def get_absolute_pressure(elevation: int, data: float) -> float: } command_decoders: dict[str, CommandDecode] = { - str(COMMAND_UUID_WAVE_2): WaveRadonCommandDecode(), - str(COMMAND_UUID_WAVE_PLUS): WavePlusCommandDecode(), + str(COMMAND_UUID_WAVE_2): WaveRadonAndPlusCommandDecode(), + str(COMMAND_UUID_WAVE_PLUS): WaveRadonAndPlusCommandDecode(), str(COMMAND_UUID_WAVE_MINI): WaveMiniCommandDecode(), } diff --git a/tests/test_absolute_pressure.py b/tests/test_absolute_pressure.py new file mode 100644 index 0000000..597e328 --- /dev/null +++ b/tests/test_absolute_pressure.py @@ -0,0 +1,8 @@ +import pytest + +from airthings_ble.parser import get_absolute_pressure + + +def test_radon_level(): + assert get_absolute_pressure(elevation=0, data=1000) == 1000 + assert get_absolute_pressure(elevation=100, data=1000) == 1011.94 diff --git a/tests/test_radon_level.py b/tests/test_radon_level.py new file mode 100644 index 0000000..fc22ea1 --- /dev/null +++ b/tests/test_radon_level.py @@ -0,0 +1,14 @@ +import pytest + +from airthings_ble.parser import get_radon_level + + +def test_radon_level(): + assert get_radon_level(0) == "very low" + assert get_radon_level(49) == "very low" + assert get_radon_level(50) == "low" + assert get_radon_level(99) == "low" + assert get_radon_level(100) == "moderate" + assert get_radon_level(299) == "moderate" + assert get_radon_level(300) == "high" + assert get_radon_level(1000) == "high" diff --git a/tests/test_wave_plus.py b/tests/test_wave_plus.py new file mode 100644 index 0000000..d8525ad --- /dev/null +++ b/tests/test_wave_plus.py @@ -0,0 +1,97 @@ +import logging + +import pytest + +from airthings_ble.parser import ( + WaveMiniCommandDecode, + WaveRadonAndPlusCommandDecode, + _decode_wave_illum_accel, + _decode_wave_mini, + _decode_wave_plus, + _decode_wave_radon, + get_absolute_pressure, + get_radon_level, +) + +_LOGGER = logging.getLogger(__name__) + + +def test_wave_plus_command_decode(): + """Test wave plus command decode.""" + decode = WaveRadonAndPlusCommandDecode() + assert decode.decode_data( + logger=_LOGGER, + raw_data=bytearray.fromhex( + "6d00600c04000100008211ff00000000c04c20001f3560007006B80B0900" + ), + ) == {"battery": 3.0} + + +def test_wave_plus_sensor_data(): + """Test wave plus sensor data.""" + raw_data = bytearray.fromhex("01380d800b002200bd094cc31d036c0000007d05") + + # Call the function + decoded_data = _decode_wave_plus(name="Plus", format_type="<4B8H", scale=1.0)( + raw_data + ) + + assert decoded_data["humidity"] == 28.0 + assert decoded_data["radon_1day_avg"] == 11 + assert decoded_data["radon_longterm_avg"] == 34 + assert decoded_data["temperature"] == 24.93 + assert decoded_data["voc"] == 108 + assert decoded_data["co2"] == 797 + assert decoded_data["illuminance_pct"] == 13 + assert decoded_data["rel_atm_pressure"] == 999.92 + + +def test_wave_radon_sensor_data(): + """Test wave plus sensor data.""" + raw_data = bytearray.fromhex("013860f009001100a709ffffffffffff0000ffff") + + decoded_data = _decode_wave_radon(name="Wave2", format_type="<4B8H", scale=1.0)( + raw_data + ) + + assert decoded_data["humidity"] == 28.0 + assert decoded_data["radon_1day_avg"] == 9 + assert decoded_data["radon_longterm_avg"] == 17 + assert decoded_data["temperature"] == 24.71 + + +def test_wave_mini_command_decode(): + """Test wave Mini command decode.""" + decode = WaveMiniCommandDecode() + assert decode.decode_data( + logger=_LOGGER, + raw_data=bytearray.fromhex( + "6d00d595000000090002030407070a00ffff0013000224008e0094110700ffffffff" + ), + ) == {"battery": 4.5} + + +def test_wave_mini_sensor_data(): + """Test Wave Mini sensor data.""" + raw_data = bytearray.fromhex("6f00977470c30d0b2d010200fefa0700ffffffff") + + decoded_data = _decode_wave_mini(name="WaveMini", format_type="<2B5HLL", scale=1.0)( + raw_data + ) + + assert decoded_data["humidity"] == 28.29 + assert decoded_data["rel_atm_pressure"] == 1000.64 + assert decoded_data["temperature"] == 25.32 + assert decoded_data["voc"] == 301.0 + assert decoded_data["rel_atm_pressure"] == 1000.64 + + +def test_wave_gen_1_illuminance_and_accelerometer(): + """Test Wave Gen 1 illuminance and accelerometer.""" + raw_data = bytearray.fromhex("b20c") + + decoded_data = _decode_wave_illum_accel( + name="illuminance_accelerometer", format_type="BB", scale=1.0 + )(raw_data) + + assert decoded_data["illuminance"] == 178.0 From b2a691b21832970e57d314ad0c3837b2c35f6ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 12:49:00 +0100 Subject: [PATCH 12/24] Update tests and linting --- airthings_ble/device_type.py | 19 +++++++++++++++++++ airthings_ble/parser.py | 17 +++++++---------- tests/test_device_type.py | 13 +++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 1b80db1..c824595 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -2,11 +2,30 @@ class AirthingsDeviceType(Enum): + UNKNOWN = 0 WAVE_GEN_1 = "2900" WAVE_MINI = "2920" WAVE_PLUS = "2930" WAVE_RADON = "2950" + def __new__(cls, value): + obj = object.__new__(cls) + obj._value_ = value + obj._raw_value = value + return obj + + @classmethod + def from_raw_value(cls, raw_value): + for member in cls.__members__.values(): + if member.raw_value == raw_value: + return member + return cls.UNKNOWN + + @property + def raw_value(self): + return self._raw_value + + @property def product_name(self): if self == AirthingsDeviceType.WAVE_GEN_1: return "Wave Gen 1" diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index dfc9273..6dbbc78 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -225,7 +225,6 @@ def handler(raw_data: bytearray) -> dict[str, float | None | str]: vals = _decode_base(name, format_type, scale)(raw_data) val = vals[name] data: dict[str, float | None | str] = {} - # data["illuminance"] = validate_value(val[0] * scale, max_value=PERCENTAGE_MAX) data["illuminance"] = val[0] * scale data["accelerometer"] = str(val[1] * scale) return data @@ -248,13 +247,13 @@ class CommandDecode: format_type: str def decode_data( - self, logger: Logger, raw_data: bytearray | None + self, logger: Logger, raw_data: bytearray | None # pylint: disable=unused-argument ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" logger.debug("Command decoder not implemented, pass") - pass def validate_data(self, logger: Logger, raw_data: bytearray | None) -> None: + """Validate data. Make sure the data is for the command.""" if raw_data is None: logger.debug("Validate data: No data received") return None @@ -476,6 +475,7 @@ def friendly_name(self) -> str: # pylint: disable=too-many-locals # pylint: disable=too-many-branches +# pylint: disable=too-few-public-methods class AirthingsBluetoothDeviceData: """Data for Airthings BLE sensors.""" @@ -505,16 +505,12 @@ async def _get_device_characteristics( self.logger.debug("Get device characteristics exception: %s", err) return - try: - self.logger.debug("Model number: %s", data.decode("utf-8")) - device_info.model = AirthingsDeviceType(data.decode("utf-8")) - except ValueError: - device_info.model = None - device_info.model_raw = data.decode("utf-8") + device_info.model = AirthingsDeviceType.from_raw_value(data.decode("utf-8")) + if device_info.model == AirthingsDeviceType.UNKNOWN: self.logger.warning( "Could not map model number to model name, " "most likely an unsupported device: %s", - device_info.model, + data.decode("utf-8"), ) characteristics = _CHARS_BY_MODELS.get( @@ -660,6 +656,7 @@ async def _get_service_characteristics( sensors.update( { + "illuminance": command_sensor_data.get("illuminance"), "battery": bat_pct, } ) diff --git a/tests/test_device_type.py b/tests/test_device_type.py index b25fdd8..9fd2eac 100644 --- a/tests/test_device_type.py +++ b/tests/test_device_type.py @@ -5,16 +5,21 @@ def test_device_type(): """Test device type.""" - assert AirthingsDeviceType.WAVE_MINI.product_name() == "Wave Mini" - assert AirthingsDeviceType.WAVE_PLUS.product_name() == "Wave Plus" - assert AirthingsDeviceType.WAVE_RADON.product_name() == "Wave Radon" - assert AirthingsDeviceType.WAVE_GEN_1.product_name() == "Wave Gen 1" + assert AirthingsDeviceType.WAVE_MINI.product_name == "Wave Mini" + assert AirthingsDeviceType.WAVE_PLUS.product_name == "Wave Plus" + assert AirthingsDeviceType.WAVE_RADON.product_name == "Wave Radon" + assert AirthingsDeviceType.WAVE_GEN_1.product_name == "Wave Gen 1" assert AirthingsDeviceType("2900") == AirthingsDeviceType.WAVE_GEN_1 assert AirthingsDeviceType("2920") == AirthingsDeviceType.WAVE_MINI assert AirthingsDeviceType("2930") == AirthingsDeviceType.WAVE_PLUS assert AirthingsDeviceType("2950") == AirthingsDeviceType.WAVE_RADON with pytest.raises(ValueError): AirthingsDeviceType("1234") + assert AirthingsDeviceType.from_raw_value("2900") == AirthingsDeviceType.WAVE_GEN_1 + assert AirthingsDeviceType.from_raw_value("2920") == AirthingsDeviceType.WAVE_MINI + assert AirthingsDeviceType.from_raw_value("2930") == AirthingsDeviceType.WAVE_PLUS + assert AirthingsDeviceType.from_raw_value("2950") == AirthingsDeviceType.WAVE_RADON + assert AirthingsDeviceType.from_raw_value("1234") == AirthingsDeviceType.UNKNOWN def test_battery_calculation(): From d583a5e836ac88e26fbb3d5df010a7c53ba0416c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 12:58:58 +0100 Subject: [PATCH 13/24] Fix linters --- airthings_ble/device_type.py | 81 ++++++++++++++++++++---------------- airthings_ble/parser.py | 15 ++++--- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index c824595..1e3857c 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -1,7 +1,10 @@ +"""Airthings device types.""" from enum import Enum class AirthingsDeviceType(Enum): + """Airthings device types.""" + UNKNOWN = 0 WAVE_GEN_1 = "2900" WAVE_MINI = "2920" @@ -9,6 +12,7 @@ class AirthingsDeviceType(Enum): WAVE_RADON = "2950" def __new__(cls, value): + """Create new device type.""" obj = object.__new__(cls) obj._value_ = value obj._raw_value = value @@ -16,6 +20,7 @@ def __new__(cls, value): @classmethod def from_raw_value(cls, raw_value): + """Get device type from raw value.""" for member in cls.__members__.values(): if member.raw_value == raw_value: return member @@ -23,50 +28,56 @@ def from_raw_value(cls, raw_value): @property def raw_value(self): + """Get raw value.""" return self._raw_value @property def product_name(self): + """Get product name.""" if self == AirthingsDeviceType.WAVE_GEN_1: return "Wave Gen 1" - elif self == AirthingsDeviceType.WAVE_MINI: + if self == AirthingsDeviceType.WAVE_MINI: return "Wave Mini" - elif self == AirthingsDeviceType.WAVE_PLUS: + if self == AirthingsDeviceType.WAVE_PLUS: return "Wave Plus" - elif self == AirthingsDeviceType.WAVE_RADON: + if self == AirthingsDeviceType.WAVE_RADON: return "Wave Radon" - else: - return "Unknown" + return "Unknown" def battery_percentage(self, vin: float) -> int: + """Calculate battery percentage based on voltage.""" if self == AirthingsDeviceType.WAVE_MINI: - # The only device that has a different battery voltage range (3 x AAA) - if vin >= 4.50: - return 100 - elif 4.20 <= vin < 4.50: - return int((vin - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) - elif 3.90 <= vin < 4.20: - return int((vin - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) - elif 3.75 <= vin < 3.90: - return int((vin - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) - elif 3.30 <= vin < 3.75: - return int((vin - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) - elif 2.40 <= vin < 3.30: - return int((vin - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) - return 0 - else: - # All other devices (2 x AA) - if vin >= 3.00: - return 100 - elif 2.80 <= vin < 3.00: - return int((vin - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) - elif 2.60 <= vin < 2.80: - return int((vin - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) - elif 2.50 <= vin < 2.60: - return int((vin - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) - elif 2.20 <= vin < 2.50: - return int((vin - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) - elif 2.10 <= vin < 2.20: - return int((vin - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) - else: - return 0 + return self._battery_percentage_wave_mini(vin) + return self._battery_percentage_wave_radon_and_plus(vin) + + # pylint: disable=too-many-return-statements + def _battery_percentage_wave_radon_and_plus(self, vin: float) -> int: + if vin >= 3.00: + return 100 + if 2.80 <= vin < 3.00: + return int((vin - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) + if 2.60 <= vin < 2.80: + return int((vin - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) + if 2.50 <= vin < 2.60: + return int((vin - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) + if 2.20 <= vin < 2.50: + return int((vin - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) + if 2.10 <= vin < 2.20: + return int((vin - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) + return 0 + + # pylint: disable=too-many-return-statements + def _battery_percentage_wave_mini(self, vin: float) -> int: + if vin >= 4.50: + return 100 + if 4.20 <= vin < 4.50: + return int((vin - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) + if 3.90 <= vin < 4.20: + return int((vin - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) + if 3.75 <= vin < 3.90: + return int((vin - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) + if 3.30 <= vin < 3.75: + return int((vin - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) + if 2.40 <= vin < 3.30: + return int((vin - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) + return 0 diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 6dbbc78..4f56168 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -247,7 +247,9 @@ class CommandDecode: format_type: str def decode_data( - self, logger: Logger, raw_data: bytearray | None # pylint: disable=unused-argument + self, + logger: Logger, + raw_data: bytearray | None, # pylint: disable=unused-argument ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" logger.debug("Command decoder not implemented, pass") @@ -456,7 +458,7 @@ class AirthingsDeviceInfo: def friendly_name(self) -> str: """Generate a name for the device.""" - return f"Airthings {self.model.product_name()}" + return f"Airthings {self.model.product_name}" @dataclasses.dataclass @@ -470,7 +472,7 @@ class AirthingsDevice(AirthingsDeviceInfo): def friendly_name(self) -> str: """Generate a name for the device.""" - return f"Airthings {self.model.product_name()}" + return f"Airthings {self.model.product_name}" # pylint: disable=too-many-locals @@ -514,7 +516,7 @@ async def _get_device_characteristics( ) characteristics = _CHARS_BY_MODELS.get( - device_info.model.value, device_info_characteristics + device_info.model.raw_value, device_info_characteristics ) for characteristic in characteristics: if did_first_sync and characteristic.name != "firmware_rev": @@ -546,7 +548,7 @@ async def _get_device_characteristics( ) if ( - device_info.model.value == AirthingsDeviceType.WAVE_GEN_1 + device_info.model.raw_value == AirthingsDeviceType.WAVE_GEN_1 and device_info.name and not device_info.identifier ): @@ -625,9 +627,6 @@ async def _get_service_characteristics( decoder = command_decoders[uuid_str] command_data_receiver = decoder.make_data_receiver() - self.logger.debug("Model: %s", device.model.name) - self.logger.debug("Command: %s", decoder.cmd.hex()) - # Set up the notification handlers await client.start_notify(characteristic, command_data_receiver) # send command to this 'indicate' characteristic From 85645ae7d58dc98677d88ebf89d2b49aaa191152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 13:03:46 +0100 Subject: [PATCH 14/24] Fix lint --- airthings_ble/parser.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 4f56168..17071f0 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -393,10 +393,7 @@ def get_absolute_pressure(elevation: int, data: float) -> float: return data + round(offset, 2) -sensor_decoders: dict[ - str, - Callable[[bytearray], dict[str, float | None | str]], -] = { +sensor_decoders: dict[str, Callable[[bytearray], dict[str, float | None | str]],] = { str(CHAR_UUID_DATETIME): _decode_wave(name="date_time", format_type="H5B", scale=0), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", From 942983e47a0ceab8ab496cb2dcb616802ff78d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 13:57:17 +0100 Subject: [PATCH 15/24] Fix linting --- airthings_ble/device_type.py | 18 +++++----- airthings_ble/parser.py | 58 +++++++++++++++++---------------- tests/test_absolute_pressure.py | 2 -- tests/test_device_type.py | 1 + tests/test_radon_level.py | 2 -- tests/test_validate_value.py | 2 -- tests/test_wave_plus.py | 4 --- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 1e3857c..701aba7 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -11,28 +11,30 @@ class AirthingsDeviceType(Enum): WAVE_PLUS = "2930" WAVE_RADON = "2950" - def __new__(cls, value): + def __new__(cls, value: str) -> "AirthingsDeviceType": """Create new device type.""" obj = object.__new__(cls) obj._value_ = value - obj._raw_value = value return obj @classmethod - def from_raw_value(cls, raw_value): + def from_raw_value(cls, raw_value: str) -> "AirthingsDeviceType": """Get device type from raw value.""" + for member in cls.__members__.values(): - if member.raw_value == raw_value: + if member.value == raw_value: return member - return cls.UNKNOWN + unknown = cls.UNKNOWN + unknown._value_ = raw_value + return unknown @property - def raw_value(self): + def raw_value(self) -> str: """Get raw value.""" - return self._raw_value + return self._value_ @property - def product_name(self): + def product_name(self) -> str: """Get product name.""" if self == AirthingsDeviceType.WAVE_GEN_1: return "Wave Gen 1" diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 17071f0..919428a 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -253,8 +253,11 @@ def decode_data( ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" logger.debug("Command decoder not implemented, pass") + return None - def validate_data(self, logger: Logger, raw_data: bytearray | None) -> None: + def validate_data( + self, logger: Logger, raw_data: bytearray | None + ) -> Optional[Any]: """Validate data. Make sure the data is for the command.""" if raw_data is None: logger.debug("Validate data: No data received") @@ -293,7 +296,7 @@ def make_data_receiver(self) -> _NotificationReceiver: class WaveRadonAndPlusCommandDecode(CommandDecode): """Decoder for the Wave Plus command response""" - def __init__(self): + def __init__(self) -> None: """Initialize command decoder""" self.format_type = " None: """Initialize command decoder""" self.format_type = "<2L4B2HL4HL" @@ -326,7 +328,6 @@ def decode_data( if val := self.validate_data(logger, raw_data): res = {} res["battery"] = val[11] / 1000.0 - logger.debug("Battery voltage: %s", res["battery"]) return res @@ -393,7 +394,10 @@ def get_absolute_pressure(elevation: int, data: float) -> float: return data + round(offset, 2) -sensor_decoders: dict[str, Callable[[bytearray], dict[str, float | None | str]],] = { +sensor_decoders: dict[ + str, + Callable[[bytearray], dict[str, float | None | str]], +] = { str(CHAR_UUID_DATETIME): _decode_wave(name="date_time", format_type="H5B", scale=0), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", @@ -446,7 +450,7 @@ class AirthingsDeviceInfo: manufacturer: str = "" hw_version: str = "" sw_version: str = "" - model: Optional[AirthingsDeviceType] = None + model: AirthingsDeviceType = AirthingsDeviceType.UNKNOWN name: str = "" identifier: str = "" address: str = "" @@ -545,7 +549,7 @@ async def _get_device_characteristics( ) if ( - device_info.model.raw_value == AirthingsDeviceType.WAVE_GEN_1 + device_info.model == AirthingsDeviceType.WAVE_GEN_1 and device_info.name and not device_info.identifier ): @@ -635,27 +639,21 @@ async def _get_service_characteristics( except asyncio.TimeoutError: self.logger.warning("Timeout getting command data.") - self.logger.debug( - "Starting to decode data for model %s", - device.model, - ) command_sensor_data = decoder.decode_data( self.logger, command_data_receiver.message ) if command_sensor_data is not None: - # calculate battery percentage - bat_pct: int | None = None + new_values: dict[str, float | str | None] = {} if (bat_data := command_sensor_data.get("battery")) is not None: - self.logger.debug("Battery voltage: %s", bat_data) - bat_pct = device.model.battery_percentage(float(bat_data)) - - sensors.update( - { - "illuminance": command_sensor_data.get("illuminance"), - "battery": bat_pct, - } - ) + new_values["battery"] = device.model.battery_percentage( + float(bat_data) + ) + + if illuminance := command_sensor_data.get("illuminance"): + new_values["illuminance"] = illuminance + + sensors.update(new_values) # Stop notification handler await client.stop_notify(characteristic) @@ -673,11 +671,15 @@ async def update_device(self, ble_device: BLEDevice) -> AirthingsDevice: device = AirthingsDevice() loop = asyncio.get_running_loop() disconnect_future = loop.create_future() - client: BleakClientWithServiceCache = await establish_connection( # type: ignore[assignment] # pylint: disable=line-too-long - BleakClientWithServiceCache, - ble_device, - ble_device.address, - disconnected_callback=partial(self._handle_disconnect, disconnect_future), + client: BleakClientWithServiceCache = ( + await establish_connection( # pylint: disable=line-too-long + BleakClientWithServiceCache, + ble_device, + ble_device.address, + disconnected_callback=partial( + self._handle_disconnect, disconnect_future + ), + ) ) try: async with interrupt( diff --git a/tests/test_absolute_pressure.py b/tests/test_absolute_pressure.py index 597e328..fc5a39b 100644 --- a/tests/test_absolute_pressure.py +++ b/tests/test_absolute_pressure.py @@ -1,5 +1,3 @@ -import pytest - from airthings_ble.parser import get_absolute_pressure diff --git a/tests/test_device_type.py b/tests/test_device_type.py index 9fd2eac..3fe0bdd 100644 --- a/tests/test_device_type.py +++ b/tests/test_device_type.py @@ -20,6 +20,7 @@ def test_device_type(): assert AirthingsDeviceType.from_raw_value("2930") == AirthingsDeviceType.WAVE_PLUS assert AirthingsDeviceType.from_raw_value("2950") == AirthingsDeviceType.WAVE_RADON assert AirthingsDeviceType.from_raw_value("1234") == AirthingsDeviceType.UNKNOWN + assert AirthingsDeviceType.from_raw_value("1234").raw_value == "1234" def test_battery_calculation(): diff --git a/tests/test_radon_level.py b/tests/test_radon_level.py index fc22ea1..df4c218 100644 --- a/tests/test_radon_level.py +++ b/tests/test_radon_level.py @@ -1,5 +1,3 @@ -import pytest - from airthings_ble.parser import get_radon_level diff --git a/tests/test_validate_value.py b/tests/test_validate_value.py index 0bae47d..0cb6857 100644 --- a/tests/test_validate_value.py +++ b/tests/test_validate_value.py @@ -1,5 +1,3 @@ -import pytest - from airthings_ble.const import CO2_MAX, PERCENTAGE_MAX, RADON_MAX from airthings_ble.parser import validate_value diff --git a/tests/test_wave_plus.py b/tests/test_wave_plus.py index d8525ad..32c90e2 100644 --- a/tests/test_wave_plus.py +++ b/tests/test_wave_plus.py @@ -1,7 +1,5 @@ import logging -import pytest - from airthings_ble.parser import ( WaveMiniCommandDecode, WaveRadonAndPlusCommandDecode, @@ -9,8 +7,6 @@ _decode_wave_mini, _decode_wave_plus, _decode_wave_radon, - get_absolute_pressure, - get_radon_level, ) _LOGGER = logging.getLogger(__name__) From d6fbe5c87df9d4eb3e8739984d83303a9d4ad468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 13:59:13 +0100 Subject: [PATCH 16/24] Another lint fix --- airthings_ble/parser.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 919428a..dabeb98 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -394,10 +394,7 @@ def get_absolute_pressure(elevation: int, data: float) -> float: return data + round(offset, 2) -sensor_decoders: dict[ - str, - Callable[[bytearray], dict[str, float | None | str]], -] = { +sensor_decoders: dict[str, Callable[[bytearray], dict[str, float | None | str]],] = { str(CHAR_UUID_DATETIME): _decode_wave(name="date_time", format_type="H5B", scale=0), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", From 390d90228fe6172b29ea8625c33a011f04eb4008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 14:29:53 +0100 Subject: [PATCH 17/24] Update device type --- airthings_ble/device_type.py | 9 ++------- airthings_ble/parser.py | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 701aba7..9407047 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -14,7 +14,7 @@ class AirthingsDeviceType(Enum): def __new__(cls, value: str) -> "AirthingsDeviceType": """Create new device type.""" obj = object.__new__(cls) - obj._value_ = value + obj.raw_value = value return obj @classmethod @@ -25,14 +25,9 @@ def from_raw_value(cls, raw_value: str) -> "AirthingsDeviceType": if member.value == raw_value: return member unknown = cls.UNKNOWN - unknown._value_ = raw_value + unknown.raw_value = raw_value return unknown - @property - def raw_value(self) -> str: - """Get raw value.""" - return self._value_ - @property def product_name(self) -> str: """Get product name.""" diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index dabeb98..506c09b 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -253,7 +253,6 @@ def decode_data( ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" logger.debug("Command decoder not implemented, pass") - return None def validate_data( self, logger: Logger, raw_data: bytearray | None From de0f6a67413412ee7965b92beff14f47aae1c248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 15:15:40 +0100 Subject: [PATCH 18/24] Refactor device type --- airthings_ble/device_type.py | 18 ++++++++++-------- airthings_ble/parser.py | 8 ++++++-- tests/test_device_type.py | 11 +++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 9407047..01475fb 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -11,6 +11,8 @@ class AirthingsDeviceType(Enum): WAVE_PLUS = "2930" WAVE_RADON = "2950" + raw_value: str # pylint: disable=invalid-name + def __new__(cls, value: str) -> "AirthingsDeviceType": """Create new device type.""" obj = object.__new__(cls) @@ -18,15 +20,15 @@ def __new__(cls, value: str) -> "AirthingsDeviceType": return obj @classmethod - def from_raw_value(cls, raw_value: str) -> "AirthingsDeviceType": + def from_raw_value(cls, value: str) -> "AirthingsDeviceType": """Get device type from raw value.""" - - for member in cls.__members__.values(): - if member.value == raw_value: - return member - unknown = cls.UNKNOWN - unknown.raw_value = raw_value - return unknown + for device_type in cls: + if device_type.value == value: + device_type.raw_value = value + return device_type + unknown_device = AirthingsDeviceType.UNKNOWN + unknown_device.raw_value = value + return unknown_device @property def product_name(self) -> str: diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 506c09b..25b51fb 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -252,7 +252,8 @@ def decode_data( raw_data: bytearray | None, # pylint: disable=unused-argument ) -> dict[str, float | str | None] | None: """Decoder returns dict with battery""" - logger.debug("Command decoder not implemented, pass") + logger.debug("Command decoder not implemented") + return {} def validate_data( self, logger: Logger, raw_data: bytearray | None @@ -393,7 +394,10 @@ def get_absolute_pressure(elevation: int, data: float) -> float: return data + round(offset, 2) -sensor_decoders: dict[str, Callable[[bytearray], dict[str, float | None | str]],] = { +sensor_decoders: dict[ + str, + Callable[[bytearray], dict[str, float | None | str]], +] = { str(CHAR_UUID_DATETIME): _decode_wave(name="date_time", format_type="H5B", scale=0), str(CHAR_UUID_HUMIDITY): _decode_attr( name="humidity", diff --git a/tests/test_device_type.py b/tests/test_device_type.py index 3fe0bdd..3716431 100644 --- a/tests/test_device_type.py +++ b/tests/test_device_type.py @@ -15,12 +15,11 @@ def test_device_type(): assert AirthingsDeviceType("2950") == AirthingsDeviceType.WAVE_RADON with pytest.raises(ValueError): AirthingsDeviceType("1234") - assert AirthingsDeviceType.from_raw_value("2900") == AirthingsDeviceType.WAVE_GEN_1 - assert AirthingsDeviceType.from_raw_value("2920") == AirthingsDeviceType.WAVE_MINI - assert AirthingsDeviceType.from_raw_value("2930") == AirthingsDeviceType.WAVE_PLUS - assert AirthingsDeviceType.from_raw_value("2950") == AirthingsDeviceType.WAVE_RADON - assert AirthingsDeviceType.from_raw_value("1234") == AirthingsDeviceType.UNKNOWN - assert AirthingsDeviceType.from_raw_value("1234").raw_value == "1234" + + unknown_device = AirthingsDeviceType.from_raw_value("1234") + assert unknown_device == AirthingsDeviceType.UNKNOWN + assert unknown_device.product_name == "Unknown" + assert unknown_device.raw_value == "1234" def test_battery_calculation(): From 09e30eb51ded39bf22f152992b0eda7b5882c5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 29 Jan 2024 15:59:49 +0100 Subject: [PATCH 19/24] Update tools --- airthings_ble/__init__.py | 1 + airthings_ble/device_type.py | 1 + airthings_ble/parser.py | 2 +- poetry.lock | 556 ++++++++++++++--------------------- pyproject.toml | 10 +- 5 files changed, 230 insertions(+), 340 deletions(-) diff --git a/airthings_ble/__init__.py b/airthings_ble/__init__.py index 7b05733..9dd7a55 100644 --- a/airthings_ble/__init__.py +++ b/airthings_ble/__init__.py @@ -1,4 +1,5 @@ """Parser for Airthings BLE advertisements.""" + from __future__ import annotations from .parser import AirthingsBluetoothDeviceData, AirthingsDevice diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 01475fb..75de064 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -1,4 +1,5 @@ """Airthings device types.""" + from enum import Enum diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index 25b51fb..cafdff3 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -673,7 +673,7 @@ async def update_device(self, ble_device: BLEDevice) -> AirthingsDevice: disconnect_future = loop.create_future() client: BleakClientWithServiceCache = ( await establish_connection( # pylint: disable=line-too-long - BleakClientWithServiceCache, + BleakClientWithServiceCache, # type: ignore ble_device, ble_device.address, disconnected_callback=partial( diff --git a/poetry.lock b/poetry.lock index 594c06c..f3f2e61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiofiles" @@ -13,87 +13,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, + {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, + {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, + {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, + {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, + {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, + {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, + {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, + {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, + {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, + {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, + {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, ] [package.dependencies] @@ -123,22 +123,17 @@ frozenlist = ">=1.1.0" [[package]] name = "astroid" -version = "2.15.8" +version = "3.0.2" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.8.0" files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, + {file = "astroid-3.0.2-py3-none-any.whl", hash = "sha256:d6e62862355f60e716164082d6b4b041d38e2a8cf1c7cd953ded5108bac8ff5c"}, + {file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"}, ] [package.dependencies] -lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] [[package]] name = "async-interrupt" @@ -183,36 +178,47 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "black" -version = "22.12.0" +version = "24.1.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-24.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c"}, + {file = "black-24.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445"}, + {file = "black-24.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a"}, + {file = "black-24.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4"}, + {file = "black-24.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"}, + {file = "black-24.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8"}, + {file = "black-24.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161"}, + {file = "black-24.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d"}, + {file = "black-24.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8"}, + {file = "black-24.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e"}, + {file = "black-24.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6"}, + {file = "black-24.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b"}, + {file = "black-24.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62"}, + {file = "black-24.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5"}, + {file = "black-24.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6"}, + {file = "black-24.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717"}, + {file = "black-24.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9"}, + {file = "black-24.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024"}, + {file = "black-24.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2"}, + {file = "black-24.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac"}, + {file = "black-24.1.1-py3-none-any.whl", hash = "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168"}, + {file = "black-24.1.1.tar.gz", hash = "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -324,63 +330,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.0" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, - {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, - {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, - {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, - {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, - {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, - {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, - {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, - {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, - {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, - {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, - {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, - {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, - {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, ] [package.dependencies] @@ -434,17 +440,18 @@ files = [ [[package]] name = "dill" -version = "0.3.7" +version = "0.3.8" description = "serialize all of Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, - {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, ] [package.extras] graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "exceptiongroup" @@ -582,52 +589,6 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] -[[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, -] - [[package]] name = "mac-vendor-lookup" version = "0.1.12" @@ -741,44 +702,49 @@ files = [ [[package]] name = "mypy" -version = "0.971" +version = "1.8.0" description = "Optional static typing for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, - {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, - {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, - {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, - {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, - {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, - {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, - {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, - {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, - {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, - {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, - {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, - {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, - {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, - {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, - {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, - {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, - {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, - {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -831,13 +797,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -846,23 +812,24 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pylint" -version = "2.17.7" +version = "3.0.3" description = "python code static checker" optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.8.0" files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, + {file = "pylint-3.0.3-py3-none-any.whl", hash = "sha256:7a1585285aefc5165db81083c3e06363a27448f6b467b3b0f30dbd0ac1f73810"}, + {file = "pylint-3.0.3.tar.gz", hash = "sha256:58c2398b0301e049609a8429789ec6edf3aabe9b6c5fec916acd18639c16de8b"}, ] [package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" +astroid = ">=3.0.1,<=3.1.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] -isort = ">=4.2.5,<6" +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -949,13 +916,13 @@ pyobjc-core = ">=9.2" [[package]] name = "pytest" -version = "7.4.4" +version = "8.0.0" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, + {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, ] [package.dependencies] @@ -963,7 +930,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.3.0,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] @@ -971,13 +938,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-cov" -version = "3.0.0" +version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] @@ -1031,85 +998,6 @@ files = [ {file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"}, ] -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - [[package]] name = "yarl" version = "1.9.4" @@ -1216,4 +1104,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "360836f1d1a89bd5f31af7a38a923c6bd8af8a0701b8d8948516ec58d3c5abc6" +content-hash = "c0b16bfe27d11d77e6af5cbbe7a4991bb40d513d265114e944baa1d1060c8e60" diff --git a/pyproject.toml b/pyproject.toml index 5759ad1..5ed4ec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,11 +32,11 @@ async-interrupt = ">=1.1.1" async-timeout = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [tool.poetry.dev-dependencies] -pytest = "^7.0" -pytest-cov = "^3.0" -black = {version = "^22.6.0", allow-prereleases = true} -mypy = "^0.971" -pylint = "^2.14.5" +pytest = "^8.0" +pytest-cov = "^4.1" +black = {version = "^24.1.1"} +mypy = "^1.8.0" +pylint = "^3.0.3" [tool.semantic_release] branch = "main" From f013b532a0ec5677af940d554d458c9c970f781e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Tue, 30 Jan 2024 00:41:18 +0100 Subject: [PATCH 20/24] Remove unnecessary logging --- airthings_ble/parser.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/airthings_ble/parser.py b/airthings_ble/parser.py index cafdff3..efe024e 100644 --- a/airthings_ble/parser.py +++ b/airthings_ble/parser.py @@ -272,12 +272,6 @@ def validate_data( ) return None - logger.debug( - "Result for command received, %s: %s", - cmd.hex(), - raw_data.hex(), - ) - if len(raw_data[2:]) != struct.calcsize(self.format_type): logger.warning( "Wrong length data received (%s) versus expected (%s)", From ba819137a9ee9bfde1022945ad374a1df178886d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 5 Feb 2024 10:40:10 +0100 Subject: [PATCH 21/24] Rename parameter --- airthings_ble/device_type.py | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 75de064..a4665e6 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -44,40 +44,40 @@ def product_name(self) -> str: return "Wave Radon" return "Unknown" - def battery_percentage(self, vin: float) -> int: + def battery_percentage(self, voltage: float) -> int: """Calculate battery percentage based on voltage.""" if self == AirthingsDeviceType.WAVE_MINI: - return self._battery_percentage_wave_mini(vin) - return self._battery_percentage_wave_radon_and_plus(vin) + return self._battery_percentage_wave_mini(voltage) + return self._battery_percentage_wave_radon_and_plus(voltage) # pylint: disable=too-many-return-statements - def _battery_percentage_wave_radon_and_plus(self, vin: float) -> int: - if vin >= 3.00: + def _battery_percentage_wave_radon_and_plus(self, voltage: float) -> int: + if voltage >= 3.00: return 100 - if 2.80 <= vin < 3.00: - return int((vin - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) - if 2.60 <= vin < 2.80: - return int((vin - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) - if 2.50 <= vin < 2.60: - return int((vin - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) - if 2.20 <= vin < 2.50: - return int((vin - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) - if 2.10 <= vin < 2.20: - return int((vin - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) + if 2.80 <= voltage < 3.00: + return int((voltage - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) + if 2.60 <= voltage < 2.80: + return int((voltage - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) + if 2.50 <= voltage < 2.60: + return int((voltage - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) + if 2.20 <= voltage < 2.50: + return int((voltage - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) + if 2.10 <= voltage < 2.20: + return int((voltage - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) return 0 # pylint: disable=too-many-return-statements - def _battery_percentage_wave_mini(self, vin: float) -> int: - if vin >= 4.50: + def _battery_percentage_wave_mini(self, voltage: float) -> int: + if voltage >= 4.50: return 100 - if 4.20 <= vin < 4.50: - return int((vin - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) - if 3.90 <= vin < 4.20: - return int((vin - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) - if 3.75 <= vin < 3.90: - return int((vin - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) - if 3.30 <= vin < 3.75: - return int((vin - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) - if 2.40 <= vin < 3.30: - return int((vin - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) + if 4.20 <= voltage < 4.50: + return int((voltage - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) + if 3.90 <= voltage < 4.20: + return int((voltage - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) + if 3.75 <= voltage < 3.90: + return int((voltage - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) + if 3.30 <= voltage < 3.75: + return int((voltage - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) + if 2.40 <= voltage < 3.30: + return int((voltage - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) return 0 From 24e5b44e72c679b95e8b38fa07aabf59a2ab71cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 5 Feb 2024 10:40:27 +0100 Subject: [PATCH 22/24] Add more tests --- tests/test_device_type.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_device_type.py b/tests/test_device_type.py index 3716431..c575f00 100644 --- a/tests/test_device_type.py +++ b/tests/test_device_type.py @@ -16,6 +16,11 @@ def test_device_type(): with pytest.raises(ValueError): AirthingsDeviceType("1234") + assert AirthingsDeviceType.from_raw_value("2900") == AirthingsDeviceType.WAVE_GEN_1 + assert AirthingsDeviceType.from_raw_value("2920") == AirthingsDeviceType.WAVE_MINI + assert AirthingsDeviceType.from_raw_value("2930") == AirthingsDeviceType.WAVE_PLUS + assert AirthingsDeviceType.from_raw_value("2950") == AirthingsDeviceType.WAVE_RADON + unknown_device = AirthingsDeviceType.from_raw_value("1234") assert unknown_device == AirthingsDeviceType.UNKNOWN assert unknown_device.product_name == "Unknown" From 2dcf2e8173db297733cb3aa8379452ad128fb029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 5 Feb 2024 10:53:34 +0100 Subject: [PATCH 23/24] Fix pr comment --- airthings_ble/device_type.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index a4665e6..387f271 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -55,15 +55,15 @@ def _battery_percentage_wave_radon_and_plus(self, voltage: float) -> int: if voltage >= 3.00: return 100 if 2.80 <= voltage < 3.00: - return int((voltage - 2.80) / (3.00 - 2.80) * (100 - 81) + 81) + return int(self._interpolate(voltage, 2.80, 3.00, 81, 100)) if 2.60 <= voltage < 2.80: - return int((voltage - 2.60) / (2.80 - 2.60) * (81 - 53) + 53) + return int(self._interpolate(voltage, 2.60, 2.80, 53, 81)) if 2.50 <= voltage < 2.60: - return int((voltage - 2.50) / (2.60 - 2.50) * (53 - 28) + 28) + return int(self._interpolate(voltage, 2.50, 2.60, 28, 53)) if 2.20 <= voltage < 2.50: - return int((voltage - 2.20) / (2.50 - 2.20) * (28 - 5) + 5) + return int(self._interpolate(voltage, 2.20, 2.50, 5, 28)) if 2.10 <= voltage < 2.20: - return int((voltage - 2.10) / (2.20 - 2.10) * (5 - 0) + 0) + return int(self._interpolate(voltage, 2.10, 2.20, 0, 5)) return 0 # pylint: disable=too-many-return-statements @@ -71,13 +71,18 @@ def _battery_percentage_wave_mini(self, voltage: float) -> int: if voltage >= 4.50: return 100 if 4.20 <= voltage < 4.50: - return int((voltage - 4.20) / (4.50 - 4.20) * (100 - 85) + 85) + return int(self._interpolate(voltage, 4.20, 4.50, 85, 100)) if 3.90 <= voltage < 4.20: - return int((voltage - 3.90) / (4.20 - 3.90) * (85 - 62) + 62) + return int(self._interpolate(voltage, 3.90, 4.20, 62, 85)) if 3.75 <= voltage < 3.90: - return int((voltage - 3.75) / (3.90 - 3.75) * (62 - 42) + 42) + return int(self._interpolate(voltage, 3.75, 3.90, 42, 62)) if 3.30 <= voltage < 3.75: - return int((voltage - 3.30) / (3.75 - 3.30) * (42 - 23) + 23) + return int(self._interpolate(voltage, 3.30, 3.75, 23, 42)) if 2.40 <= voltage < 3.30: - return int((voltage - 2.40) / (3.30 - 2.40) * (23 - 0) + 0) + return int(self._interpolate(voltage, 2.40, 3.30, 0, 23)) return 0 + + def _interpolate( + self, x: float, x0: float, x1: float, y0: float, y1: float + ) -> float: + return (x - x0) / (x1 - x0) * (y1 - y0) + y0 From 8cf1b857ec663fc92b507da4b1bd0683e9c94c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Mon, 5 Feb 2024 11:11:39 +0100 Subject: [PATCH 24/24] Fix linting issues --- airthings_ble/device_type.py | 57 ++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/airthings_ble/device_type.py b/airthings_ble/device_type.py index 387f271..f7aff4a 100644 --- a/airthings_ble/device_type.py +++ b/airthings_ble/device_type.py @@ -47,42 +47,67 @@ def product_name(self) -> str: def battery_percentage(self, voltage: float) -> int: """Calculate battery percentage based on voltage.""" if self == AirthingsDeviceType.WAVE_MINI: - return self._battery_percentage_wave_mini(voltage) - return self._battery_percentage_wave_radon_and_plus(voltage) + return round(self._battery_percentage_wave_mini(voltage)) + return round(self._battery_percentage_wave_radon_and_plus(voltage)) # pylint: disable=too-many-return-statements - def _battery_percentage_wave_radon_and_plus(self, voltage: float) -> int: + def _battery_percentage_wave_radon_and_plus(self, voltage: float) -> float: if voltage >= 3.00: return 100 if 2.80 <= voltage < 3.00: - return int(self._interpolate(voltage, 2.80, 3.00, 81, 100)) + return self._interpolate( + voltage, voltage_range=(2.80, 3.00), percentage_range=(81, 100) + ) if 2.60 <= voltage < 2.80: - return int(self._interpolate(voltage, 2.60, 2.80, 53, 81)) + return self._interpolate( + voltage, voltage_range=(2.60, 2.80), percentage_range=(53, 81) + ) if 2.50 <= voltage < 2.60: - return int(self._interpolate(voltage, 2.50, 2.60, 28, 53)) + return self._interpolate( + voltage, voltage_range=(2.50, 2.60), percentage_range=(28, 53) + ) if 2.20 <= voltage < 2.50: - return int(self._interpolate(voltage, 2.20, 2.50, 5, 28)) + return self._interpolate( + voltage, voltage_range=(2.20, 2.50), percentage_range=(5, 28) + ) if 2.10 <= voltage < 2.20: - return int(self._interpolate(voltage, 2.10, 2.20, 0, 5)) + return self._interpolate( + voltage, voltage_range=(2.10, 2.20), percentage_range=(0, 5) + ) return 0 # pylint: disable=too-many-return-statements - def _battery_percentage_wave_mini(self, voltage: float) -> int: + def _battery_percentage_wave_mini(self, voltage: float) -> float: if voltage >= 4.50: return 100 if 4.20 <= voltage < 4.50: - return int(self._interpolate(voltage, 4.20, 4.50, 85, 100)) + return self._interpolate( + voltage=voltage, voltage_range=(4.20, 4.50), percentage_range=(85, 100) + ) if 3.90 <= voltage < 4.20: - return int(self._interpolate(voltage, 3.90, 4.20, 62, 85)) + return self._interpolate( + voltage=voltage, voltage_range=(3.90, 4.20), percentage_range=(62, 85) + ) if 3.75 <= voltage < 3.90: - return int(self._interpolate(voltage, 3.75, 3.90, 42, 62)) + return self._interpolate( + voltage=voltage, voltage_range=(3.75, 3.90), percentage_range=(42, 62) + ) if 3.30 <= voltage < 3.75: - return int(self._interpolate(voltage, 3.30, 3.75, 23, 42)) + return self._interpolate( + voltage=voltage, voltage_range=(3.30, 3.75), percentage_range=(23, 42) + ) if 2.40 <= voltage < 3.30: - return int(self._interpolate(voltage, 2.40, 3.30, 0, 23)) + return self._interpolate( + voltage=voltage, voltage_range=(2.40, 3.30), percentage_range=(0, 23) + ) return 0 def _interpolate( - self, x: float, x0: float, x1: float, y0: float, y1: float + self, + voltage: float, + voltage_range: tuple[float, float], + percentage_range: tuple[int, int], ) -> float: - return (x - x0) / (x1 - x0) * (y1 - y0) + y0 + return (voltage - voltage_range[0]) / (voltage_range[1] - voltage_range[0]) * ( + percentage_range[1] - percentage_range[0] + ) + percentage_range[0]