Bluetooth-Devices-xiaomi-ble-b47183c/0000775000175000017500000000000014772435047021567 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/src/0000775000175000017500000000000014772435047022356 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/0000775000175000017500000000000014772435047024466 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/parser.py0000664000175000017500000022621014772435047026337 0ustar billchenchinabillchenchina"""Parser for Xiaomi BLE advertisements. This file is shamlessly copied from the following repository: https://github.com/Ernst79/bleparser/blob/c42ae922e1abed2720c7fac993777e1bd59c0c93/package/bleparser/xiaomi.py MIT License applies. """ from __future__ import annotations import datetime import logging import math import struct from typing import Any from bleak import BleakClient from bleak.backends.device import BLEDevice from bleak_retry_connector import establish_connection from bluetooth_data_tools import short_address from bluetooth_sensor_state_data import BluetoothData from Cryptodome.Cipher import AES from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives.ciphers.aead import AESCCM from home_assistant_bluetooth import BluetoothServiceInfo from sensor_state_data import ( BinarySensorDeviceClass, SensorLibrary, SensorUpdate, Units, ) from .const import ( CHARACTERISTIC_BATTERY, SERVICE_HHCCJCY10, SERVICE_MIBEACON, SERVICE_SCALE1, SERVICE_SCALE2, TIMEOUT_1DAY, EncryptionScheme, ExtendedBinarySensorDeviceClass, ExtendedSensorDeviceClass, ) from .devices import DEVICE_TYPES, SLEEPY_DEVICE_MODELS from .events import EventDeviceKeys from .locks import BLE_LOCK_ACTION, BLE_LOCK_ERROR, BLE_LOCK_METHOD _LOGGER = logging.getLogger(__name__) def to_mac(addr: bytes) -> str: """Return formatted MAC address""" return ":".join(f"{i:02X}" for i in addr) def to_unformatted_mac(addr: str) -> str: """Return unformatted MAC address""" return "".join(f"{i:02X}" for i in addr[:]) def parse_event_properties( event_property: str | None, value: int ) -> dict[str, int | None] | None: """Convert event property and data to event properties.""" if event_property: return {event_property: value} return None # Structured objects for data conversions TH_STRUCT = struct.Struct(" dict[str, Any]: """Motion""" # 0x0003 is only used by MUE4094RT, which does not send motion clear. # This object is therefore added as event (motion detected). device.fire_event( key=EventDeviceKeys.MOTION, event_type="motion_detected", event_properties=None, ) return {} def obj0006( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Fingerprint""" if len(xobj) == 5: key_id_bytes = xobj[0:4] match_byte = xobj[4] if key_id_bytes == b"\x00\x00\x00\x00": key_type = "administrator" elif key_id_bytes == b"\xff\xff\xff\xff": key_type = "unknown operator" elif key_id_bytes == b"\xde\xad\xbe\xef": key_type = "invalid operator" else: key_type = str(int.from_bytes(key_id_bytes, "little")) if match_byte == 0x00: result = "match_successful" elif match_byte == 0x01: result = "match_failed" elif match_byte == 0x02: result = "timeout" elif match_byte == 0x033: result = "low_quality_too_light_fuzzy" elif match_byte == 0x04: result = "insufficient_area" elif match_byte == 0x05: result = "skin_is_too_dry" elif match_byte == 0x06: result = "skin_is_too_wet" else: result = None fingerprint = True if match_byte == 0x00 else False # Update fingerprint binary sensor device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.FINGERPRINT, native_value=fingerprint, device_class=ExtendedBinarySensorDeviceClass.FINGERPRINT, name="Fingerprint", ) # Update key_id sensor device.update_sensor( key=ExtendedSensorDeviceClass.KEY_ID, name="Key id", device_class=ExtendedSensorDeviceClass.KEY_ID, native_value=key_type, native_unit_of_measurement=None, ) # Fire Fingerprint action event if result: device.fire_event( key=EventDeviceKeys.FINGERPRINT, event_type=result, event_properties=None, ) return {} def obj0007( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Door""" door_byte = xobj[0] if door_byte == 0x00: # open the door device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_STUCK, native_value=False, # reset door stuck device_class=ExtendedBinarySensorDeviceClass.DOOR_STUCK, name="Door stuck", ) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, native_value=False, # reset knock on the door device_class=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, name="Knock on the door", ) elif door_byte == 0x01: # close the door device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=False, # reset door left open device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, native_value=False, # reset pry the door device_class=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, name="Pry the door", ) elif door_byte == 0x02: # timeout, not closed device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=True, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) elif door_byte == 0x03: # knock on the door device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, native_value=True, device_class=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, name="Knock on the door", ) elif door_byte == 0x04: # pry the door device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, native_value=True, device_class=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, name="Pry the door", ) elif door_byte == 0x05: # door stuck device.update_predefined_binary_sensor(BinarySensorDeviceClass.DOOR, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_STUCK, native_value=True, device_class=ExtendedBinarySensorDeviceClass.DOOR_STUCK, name="Door stuck", ) return {} def obj0008( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """armed away""" value = xobj[0] ^ 1 device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.ARMED, native_value=bool(value), # Armed away device_class=ExtendedBinarySensorDeviceClass.ARMED, name="Armed", ) # Lift up door handle outside the door sends this event from DSL-C08. if device_type == "DSL-C08": device.update_predefined_binary_sensor( BinarySensorDeviceClass.LOCK, bool(value) ) # Fire Lock action event device.fire_event( key=EventDeviceKeys.LOCK, event_type="lock_outside_the_door", event_properties=None, ) # # Update method sensor device.update_sensor( key=ExtendedSensorDeviceClass.LOCK_METHOD, name="Lock method", device_class=ExtendedSensorDeviceClass.LOCK_METHOD, native_value="manual", native_unit_of_measurement=None, ) return {} def obj0010( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Toothbrush""" if xobj[0] == 0: device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, native_value=True, # Toothbrush On device_class=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, name="Toothbrush", ) else: device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, native_value=False, # Toothbrush Off device_class=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, name="Toothbrush", ) if len(xobj) > 1: device.update_sensor( key=ExtendedSensorDeviceClass.COUNTER, name="Counter", native_unit_of_measurement=Units.TIME_SECONDS, device_class=ExtendedSensorDeviceClass.COUNTER, native_value=xobj[1], ) return {} def obj000a( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Body Temperature""" if len(xobj) == 2: temp = T_STRUCT(xobj)[0] if temp: device.update_predefined_sensor( SensorLibrary.TEMPERATURE__CELSIUS, temp / 100 ) return {} def obj000b( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Lock""" if len(xobj) == 9: lock_action_int = xobj[0] & 0x0F lock_method_int = xobj[0] >> 4 key_id = int.from_bytes(xobj[1:5], "little") short_key_id = key_id & 0xFFFF # Lock action (event) and lock method (sensor) if ( lock_action_int not in BLE_LOCK_ACTION or lock_method_int not in BLE_LOCK_METHOD ): return {} lock_action = BLE_LOCK_ACTION[lock_action_int][2] lock_method = BLE_LOCK_METHOD[lock_method_int] # Some specific key_ids represent an error error = BLE_LOCK_ERROR.get(key_id) if not error: if key_id == 0x00000000: key_type = "administrator" elif key_id == 0xFFFFFFFF: key_type = "unknown operator" elif key_id == 0xDEADBEEF: key_type = "invalid operator" elif key_id <= 0x7FFFFFF: # Bluetooth (up to 2147483647) key_type = f"Bluetooth key {key_id}" else: # All other key methods have only key ids up to 65536 if key_id <= 0x8001FFFF: key_type = f"Fingerprint key id {short_key_id}" elif key_id <= 0x8002FFFF: key_type = f"Password key id {short_key_id}" elif key_id <= 0x8003FFFF: key_type = f"Keys key id {short_key_id}" elif key_id <= 0x8004FFFF: key_type = f"NFC key id {short_key_id}" elif key_id <= 0x8005FFFF: key_type = f"Two-step verification key id {short_key_id}" elif key_id <= 0x8006FFFF: key_type = f"Human face key id {short_key_id}" elif key_id <= 0x8007FFFF: key_type = f"Finger veins key id {short_key_id}" elif key_id <= 0x8008FFFF: key_type = f"Palm print key id {short_key_id}" else: key_type = f"key id {short_key_id}" # Lock type and state # Lock type can be `lock` or for ZNMS17LM `lock`, `childlock` or `antilock` if device_type == "ZNMS17LM": # Lock type can be `lock`, `childlock` or `antilock` lock_type = BLE_LOCK_ACTION[lock_action_int][1] else: # Lock type can only be `lock` for other locks lock_type = "lock" lock_state = BLE_LOCK_ACTION[lock_action_int][0] # Update lock state if lock_type == "lock": device.update_predefined_binary_sensor( BinarySensorDeviceClass.LOCK, lock_state ) elif lock_type == "childlock": device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.CHILDLOCK, native_value=lock_state, device_class=ExtendedBinarySensorDeviceClass.CHILDLOCK, name="Childlock", ) elif lock_type == "antilock": device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.ANTILOCK, native_value=lock_state, device_class=ExtendedBinarySensorDeviceClass.ANTILOCK, name="Antilock", ) else: return {} # Update key_id sensor device.update_sensor( key=ExtendedSensorDeviceClass.KEY_ID, name="Key id", device_class=ExtendedSensorDeviceClass.KEY_ID, native_value=key_type, native_unit_of_measurement=None, ) # Fire Lock action event: see BLE_LOCK_ACTTION device.fire_event( key=EventDeviceKeys.LOCK, event_type=lock_action, event_properties=None, ) # # Update method sensor: see BLE_LOCK_METHOD device.update_sensor( key=ExtendedSensorDeviceClass.LOCK_METHOD, name="Lock method", device_class=ExtendedSensorDeviceClass.LOCK_METHOD, native_value=lock_method.value, native_unit_of_measurement=None, ) if error: # Fire event with the error: see BLE_LOCK_ERROR device.fire_event( key=EventDeviceKeys.ERROR, event_type=error, event_properties=None, ) return {} def obj000f( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Moving with light""" if len(xobj) == 3: illum = LIGHT_STRUCT(xobj + b"\x00")[0] device.update_predefined_binary_sensor(BinarySensorDeviceClass.MOTION, True) if device_type in ["MJYD02YL", "RTCGQ02LM"]: # MJYD02YL: 1 - moving no light, 100 - moving with light # RTCGQ02LM: 0 - moving no light, 256 - moving with light device.update_predefined_binary_sensor( BinarySensorDeviceClass.LIGHT, bool(illum >= 100) ) elif device_type == "CGPR1": # CGPR1: moving, value is illumination in lux device.update_predefined_sensor(SensorLibrary.LIGHT__LIGHT_LUX, illum) return {} def obj1001( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """button""" if len(xobj) != 3: return {} (button_type, value, press_type) = BUTTON_STRUCT(xobj) # button_type represents the pressed button or rubiks cube rotation direction remote_command = None fan_remote_command = None ven_fan_remote_command = None bathroom_remote_command = None cube_rotation = None one_btn_switch = False two_btn_switch_left = False two_btn_switch_right = False three_btn_switch_left = False three_btn_switch_middle = False three_btn_switch_right = False if button_type == 0: remote_command = "on" fan_remote_command = "fan" ven_fan_remote_command = "swing" bathroom_remote_command = "stop" one_btn_switch = True two_btn_switch_left = True three_btn_switch_left = True cube_rotation = "rotate_right" elif button_type == 1: remote_command = "off" fan_remote_command = "light" ven_fan_remote_command = "power" bathroom_remote_command = "air_exchange" two_btn_switch_right = True three_btn_switch_middle = True cube_rotation = "rotate_left" elif button_type == 2: remote_command = "brightness" fan_remote_command = "wind_speed" ven_fan_remote_command = "timer_60_minutes" bathroom_remote_command = "fan" two_btn_switch_left = True two_btn_switch_right = True three_btn_switch_right = True elif button_type == 3: remote_command = "plus" fan_remote_command = "color_temperature" ven_fan_remote_command = "increase_wind_speed" bathroom_remote_command = "increase_speed" three_btn_switch_left = True three_btn_switch_middle = True elif button_type == 4: remote_command = "M" fan_remote_command = "wind_mode" ven_fan_remote_command = "timer_30_minutes" bathroom_remote_command = "decrease_speed" three_btn_switch_middle = True three_btn_switch_right = True elif button_type == 5: remote_command = "min" fan_remote_command = "brightness" ven_fan_remote_command = "decrease_wind_speed" bathroom_remote_command = "dry" three_btn_switch_left = True three_btn_switch_right = True elif button_type == 6: bathroom_remote_command = "light" three_btn_switch_left = True three_btn_switch_middle = True three_btn_switch_right = True elif button_type == 7: bathroom_remote_command = "swing" elif button_type == 8: bathroom_remote_command = "heat" # press_type represents the type of press or rotate # for dimmers, buton_type is used to represent the type of press # for dimmers, value or button_type is used to represent the direction and number # of steps, number of presses or duration of long press button_press_type = "no_press" btn_switch_press_type = None dimmer_value: int = 0 if press_type == 0: button_press_type = "press" btn_switch_press_type = "press" elif press_type == 1: button_press_type = "double_press" btn_switch_press_type = "long_press" elif press_type == 2: button_press_type = "long_press" btn_switch_press_type = "double_press" elif press_type == 3: if button_type == 0: button_press_type = "press" dimmer_value = value if button_type == 1: button_press_type = "long_press" dimmer_value = value elif press_type == 4: if button_type == 0: if value <= 127: button_press_type = "rotate_right" dimmer_value = value else: button_press_type = "rotate_left" dimmer_value = 256 - value elif button_type <= 127: button_press_type = "rotate_right_pressed" dimmer_value = button_type else: button_press_type = "rotate_left_pressed" dimmer_value = 256 - button_type elif press_type == 5: button_press_type = "press" elif press_type == 6: button_press_type = "long_press" # return device specific output if device_type in ["RTCGQ02LM", "YLAI003", "JTYJGD03MI", "SJWS01LM"]: # RTCGQ02LM, JTYJGD03MI, SJWS01LM: press # YLAI003: press, double_press or long_press device.fire_event( key=EventDeviceKeys.BUTTON, event_type=button_press_type, event_properties=None, ) elif device_type == "XMMF01JQD": # cube_rotation: rotate_left or rotate_right device.fire_event( key=EventDeviceKeys.CUBE, event_type=cube_rotation, event_properties=None, ) elif device_type == "YLYK01YL": # Buttons: on, off, brightness, plus, min, M # Press types: press and long_press if remote_command == "on": device.update_predefined_binary_sensor(BinarySensorDeviceClass.POWER, True) elif remote_command == "off": device.update_predefined_binary_sensor(BinarySensorDeviceClass.POWER, False) device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{remote_command}", event_type=button_press_type, event_properties=None, ) elif device_type == "YLYK01YL-FANRC": # Buttons: fan, light, wind_speed, wind_mode, brightness, color_temperature # Press types: press and long_press device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{fan_remote_command}", event_type=button_press_type, event_properties=None, ) elif device_type == "YLYK01YL-VENFAN": # Buttons: swing, power, timer_30_minutes, timer_60_minutes, # increase_wind_speed, decrease_wind_speed # Press types: press and long_press device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{ven_fan_remote_command}", event_type=button_press_type, event_properties=None, ) elif device_type == "YLYB01YL-BHFRC": # Buttons: heat, air_exchange, dry, fan, swing, decrease_speed, increase_speed, # stop or light # Press types: press and long_press device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{bathroom_remote_command}", event_type=button_press_type, event_properties=None, ) elif device_type == "YLKG07YL/YLKG08YL": # Dimmer reports: press, long_press, rotate_left, rotate_right, # rotate_left_pressed or rotate_right_pressed if button_press_type == "press": # it also reports how many times you pressed the dimmer. event_property = "number_of_presses" elif button_press_type == "long_press": # it also reports the duration (in seconds) you pressed the dimmer event_property = "duration" elif button_press_type in [ "rotate_right", "rotate_left", "rotate_right_pressed", "rotate_left_pressed", ]: # it reports how far you rotate, measured in number of `steps`. event_property = "steps" else: event_property = None event_properties = parse_event_properties( event_property=event_property, value=dimmer_value ) device.fire_event( key=EventDeviceKeys.DIMMER, event_type=button_press_type, event_properties=event_properties, ) elif device_type == "K9B-1BTN": # Press types: press, double_press, long_press if one_btn_switch: device.fire_event( key=EventDeviceKeys.BUTTON, event_type=btn_switch_press_type, event_properties=None, ) elif device_type == "K9B-2BTN": # Buttons: left and/or right # Press types: press, double_press, long_press # device can send button press of multiple buttons in one message if two_btn_switch_left: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type=btn_switch_press_type, event_properties=None, ) if two_btn_switch_right: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type=btn_switch_press_type, event_properties=None, ) elif device_type == "K9B-3BTN": # Buttons: left, middle and/or right # result can be press, double_press or long_press # device can send button press of multiple buttons in one message if three_btn_switch_left: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type=btn_switch_press_type, event_properties=None, ) if three_btn_switch_middle: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_middle", event_type=btn_switch_press_type, event_properties=None, ) if three_btn_switch_right: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type=btn_switch_press_type, event_properties=None, ) return {} def obj1004( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Temperature""" if len(xobj) == 2: temp = T_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp / 10) return {} def obj1005( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Power on/off and Temperature""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.POWER, xobj[0]) device.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, xobj[1]) return {} def obj1006( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Humidity""" if len(xobj) == 2: humi = H_STRUCT(xobj)[0] if device_type in ["LYWSD03MMC", "MHO-C401"]: # To handle jagged stair stepping readings from these sensors. # https://github.com/custom-components/ble_monitor/blob/ef2e3944b9c1a635208390b8563710d0eec2a945/custom_components/ble_monitor/sensor.py#L752 # https://github.com/esphome/esphome/blob/c39f6d0738d97ecc11238220b493731ec70c701c/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp#L44C14-L44C99 # https://github.com/custom-components/ble_monitor/issues/7#issuecomment-595948254 device.update_predefined_sensor( SensorLibrary.HUMIDITY__PERCENTAGE, int(humi / 10) ) else: device.update_predefined_sensor( SensorLibrary.HUMIDITY__PERCENTAGE, humi / 10 ) return {} def obj1007( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Illuminance""" if len(xobj) == 3: illum = ILL_STRUCT(xobj + b"\x00")[0] if device_type in ["MJYD02YL", "MCCGQ02HL"]: # 100 means light, else dark (0 or 1) # MCCGQ02HL might use obj1018 for light sensor, just added here to be sure. device.update_predefined_binary_sensor( BinarySensorDeviceClass.LIGHT, illum == 100 ) elif device_type in ["HHCCJCY01", "GCLS002"]: # illumination in lux device.update_predefined_sensor(SensorLibrary.LIGHT__LIGHT_LUX, illum) return {} def obj1008( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Moisture""" device.update_predefined_sensor(SensorLibrary.MOISTURE__PERCENTAGE, xobj[0]) return {} def obj1009( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Conductivity""" if len(xobj) == 2: cond = CND_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.CONDUCTIVITY__CONDUCTIVITY, cond) return {} def obj1010( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Formaldehyde""" if len(xobj) == 2: fmdh = FMDH_STRUCT(xobj)[0] device.update_predefined_sensor( SensorLibrary.FORMALDEHYDE__CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, fmdh / 100, ) return {} def obj1012( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Power on/off""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.POWER, xobj[0]) return {} def obj1013( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Consumable (in percent)""" device.update_sensor( key=ExtendedSensorDeviceClass.CONSUMABLE, name="Consumable", native_unit_of_measurement=Units.PERCENTAGE, device_class=ExtendedSensorDeviceClass.CONSUMABLE, native_value=xobj[0], ) return {} def obj1014( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Moisture""" device.update_predefined_binary_sensor( BinarySensorDeviceClass.MOISTURE, xobj[0] > 0 ) return {} def obj1015( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Smoke""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.SMOKE, xobj[0] > 0) return {} def obj1017( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Time in seconds without motion""" if len(xobj) == 4: no_motion_time = M_STRUCT(xobj)[0] # seconds since last motion detected message # 0x1017 is sent 3 seconds after 0x000f, 5 seconds arter 0x1007 # and at 60, 120, 300, 600, 1200 and 1800 seconds after last motion. # Anything <= 30 seconds is regarded motion detected in the MiHome app. if no_motion_time <= 30: device.update_predefined_binary_sensor(BinarySensorDeviceClass.MOTION, True) else: device.update_predefined_binary_sensor( BinarySensorDeviceClass.MOTION, False ) return {} def obj1018( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Light intensity""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.LIGHT, bool(xobj[0])) return {} def obj1019( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Door/Window sensor""" open_obj = xobj[0] if open_obj == 0: # opened device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) elif open_obj == 1: # closed device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=False, # reset door left open device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) elif open_obj == 2: # closing timeout device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=True, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) elif open_obj == 3: # device reset (not implemented) return {} else: return {} return {} def obj100a( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Battery""" batt = xobj[0] volt = 2.2 + (3.1 - 2.2) * (batt / 100) device.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt) device.update_predefined_sensor( SensorLibrary.VOLTAGE__ELECTRIC_POTENTIAL_VOLT, volt ) return {} def obj100d( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Temperature and humidity""" if len(xobj) == 4: (temp, humi) = TH_STRUCT(xobj) device.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp / 10) device.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi / 10) return {} def obj100e( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Lock common attribute""" # https://iot.mi.com/new/doc/accesses/direct-access/embedded-development/ble/object-definition#%E9%94%81%E5%B1%9E%E6%80%A7 if len(xobj) == 1: # Unlock by type on some devices if device_type == "DSL-C08": lock_attribute = int.from_bytes(xobj, "little") device.update_predefined_binary_sensor( BinarySensorDeviceClass.LOCK, bool(lock_attribute & 0x01 ^ 1) ) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.CHILDLOCK, native_value=bool(lock_attribute >> 3 ^ 1), device_class=ExtendedBinarySensorDeviceClass.CHILDLOCK, name="Childlock", ) return {} def obj101b( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Timeout no movement""" # https://iot.mi.com/new/doc/accesses/direct-access/embedded-development/ble/object-definition#%E9%80%9A%E7%94%A8%E5%B1%9E%E6%80%A7 device.update_predefined_binary_sensor(BinarySensorDeviceClass.MOTION, False) return {} def obj2000( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Body temperature""" if len(xobj) == 5: (temp1, temp2, bat) = TTB_STRUCT(xobj) # Body temperature is calculated from the two measured temperatures. # Formula is based on approximation based on values in the app in # the range 36.5 - 37.8. body_temp = ( 3.71934 * pow(10, -11) * math.exp(0.69314 * temp1 / 100) - (1.02801 * pow(10, -8) * math.exp(0.53871 * temp2 / 100)) + 36.413 ) device.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, body_temp) device.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, bat) return {} def obj3003( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Brushing""" result = {} start_obj = xobj[0] if start_obj == 0: # Start of brushing device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, native_value=True, # Toothbrush On device_class=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, name="Toothbrush", ) # Start time has not been implemented start_time = struct.unpack(" dict[str, Any]: """Temperature""" temp = FLOAT_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, round(temp, 1)) return {} def obj4802( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Humidity""" device.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, xobj[0]) return {} def obj4803( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Battery""" device.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, xobj[0]) return {} def obj4804( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Opening (state)""" opening_state = xobj[0] # State of the door/window, used in combination with obj4a12 if opening_state == 1: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) elif opening_state == 2: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=False, # reset door left open device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, native_value=False, # reset device forcibly removed device_class=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, name="Device forcibly removed", ) return {} def obj4805( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Illuminance in lux""" illum = FLOAT_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.LIGHT__LIGHT_LUX, illum) return {} def obj4806( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Moisture""" device.update_predefined_binary_sensor( BinarySensorDeviceClass.MOISTURE, xobj[0] > 0 ) return {} def obj4808( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Humidity""" humi = FLOAT_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, round(humi, 1)) return {} def obj4818( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Time in seconds of no motion""" if len(xobj) == 2: (no_motion_time,) = struct.unpack(" dict[str, Any]: """From miot-spec: occupancy-status: uint8: 0 - No One, 1 - Has One""" """Translate to: occupancy: bool: 0 - Clear, 1 - Detected""" device.update_predefined_binary_sensor( BinarySensorDeviceClass.OCCUPANCY, xobj[0] > 0 ) return {} def obj4851( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """From miot-spec: has-someone-duration: uint8: 2 - 2 minutes, 5 - 5 minutes""" """Translate to: duration_detected: uint8: 2 - 2 minutes, 5 - 5 minutes""" device.update_sensor( key=ExtendedSensorDeviceClass.DURATION_DETECTED, name="Duration detected", native_unit_of_measurement=Units.TIME_MINUTES, device_class=ExtendedSensorDeviceClass.DURATION_DETECTED, native_value=xobj[0], ) return {} def obj4852( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """From miot-spec: no-one-duration: uint8: 2/5/10/30 - 2/5/10/30 minutes""" """Translate to: duration_cleared: uint8: 2/5/10/30 - 2/5/10/30 minutes""" device.update_sensor( key=ExtendedSensorDeviceClass.DURATION_CLEARED, name="Duration cleared", native_unit_of_measurement=Units.TIME_MINUTES, device_class=ExtendedSensorDeviceClass.DURATION_CLEARED, native_value=xobj[0], ) return {} def obj4a01( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Low Battery""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.BATTERY, xobj[0]) return {} def obj4a08( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Motion detected with Illuminance in lux""" (illum,) = struct.unpack("f", xobj) device.update_predefined_binary_sensor(BinarySensorDeviceClass.MOTION, True) device.update_predefined_sensor(SensorLibrary.LIGHT__LIGHT_LUX, illum) return {} def obj4a0c( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Single press""" device.fire_event( key=EventDeviceKeys.BUTTON, event_type="press", event_properties=None, ) return {} def obj4a0d( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Double press""" device.fire_event( key=EventDeviceKeys.BUTTON, event_type="double_press", event_properties=None, ) return {} def obj4a0e( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Long press""" device.fire_event( key=EventDeviceKeys.BUTTON, event_type="long_press", event_properties=None, ) return {} def obj4a0f( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Device forcibly removed""" dev_forced = xobj[0] if dev_forced == 1: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, native_value=True, device_class=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, name="Device forcibly removed", ) return {} def obj4a12( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Opening (event)""" opening_state = xobj[0] # Opening event, used in combination with obj4804 if opening_state == 1: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) elif opening_state == 2: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, False) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=False, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, native_value=False, # reset device forcibly removed device_class=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, name="Device forcibly removed", ) return {} def obj4a13( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Button press (MS1BB(MI))""" press = xobj[0] if press == 1: device.fire_event( key=EventDeviceKeys.BUTTON, event_type="press", event_properties=None, ) return {} def obj4a1a( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Door left open""" if xobj[0] == 1: device.update_predefined_binary_sensor(BinarySensorDeviceClass.OPENING, True) device.update_binary_sensor( key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, native_value=False, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, name="Door left open", ) return {} def obj4c01( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Temperature""" if len(xobj) == 4: temp = FLOAT_STRUCT(xobj)[0] device.update_predefined_sensor( SensorLibrary.TEMPERATURE__CELSIUS, round(temp, 2) ) return {} def obj4c02( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Humidity""" if len(xobj) == 1: humi = xobj[0] device.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi) return {} def obj4c03( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Battery""" device.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, xobj[0]) return {} def obj4c08( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Humidity""" if len(xobj) == 4: humi = FLOAT_STRUCT(xobj)[0] device.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi) return {} def obj4c14( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Mode""" mode = xobj[0] return {"mode": mode} def obj4e01( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Low Battery""" device.update_predefined_binary_sensor(BinarySensorDeviceClass.BATTERY, xobj[0]) return {} def obj4e0c( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Button press""" if device_type == "XMWXKG01YL": press = xobj[0] if press == 1: # left button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="press", event_properties=None, ) elif press == 2: # right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="press", event_properties=None, ) elif press == 3: # both left and right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="press", event_properties=None, ) device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="press", event_properties=None, ) elif device_type == "K9BB-1BTN": press = xobj[0] if press == 1: device.fire_event( key=EventDeviceKeys.BUTTON, event_type="press", event_properties=None, ) elif press == 8: device.fire_event( key=EventDeviceKeys.BUTTON, event_type="long_press", event_properties=None, ) elif press == 15: device.fire_event( key=EventDeviceKeys.BUTTON, event_type="double_press", event_properties=None, ) elif device_type == "XMWXKG01LM": device.fire_event( key=EventDeviceKeys.BUTTON, event_type="press", event_properties=None, ) return {} def obj4e0d( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Double Press""" if device_type == "XMWXKG01YL": press = xobj[0] if press == 1: # left button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="double_press", event_properties=None, ) elif press == 2: # right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="double_press", event_properties=None, ) elif press == 3: # both left and right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="double_press", event_properties=None, ) device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="double_press", event_properties=None, ) elif device_type == "XMWXKG01LM": device.fire_event( key=EventDeviceKeys.BUTTON, event_type="double_press", event_properties=None, ) return {} def obj4e0e( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Long Press""" if device_type == "XMWXKG01YL": press = xobj[0] if press == 1: # left button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="long_press", event_properties=None, ) elif press == 2: # right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="long_press", event_properties=None, ) elif press == 3: # both left and right button device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_left", event_type="long_press", event_properties=None, ) device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_right", event_type="long_press", event_properties=None, ) elif device_type == "XMWXKG01LM": device.fire_event( key=EventDeviceKeys.BUTTON, event_type="long_press", event_properties=None, ) return {} def obj4e1c( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Device reset""" return {"device reset": True} def obj5003( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Battery""" device.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, xobj[0]) return {} def obj5414( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Device mode (KSI and KSIBP, not used in HA)""" return {"mode": xobj[0]} def obj560c( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Button press""" if device_type not in ["KS1", "KS1BP"]: return {} button = xobj[0] if button_name := QUAD_BUTTON_TO_NAME[button]: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{button_name}", event_type="press", event_properties=None, ) return {} def obj560d( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Double button press""" if device_type not in ["KS1", "KS1BP"]: return {} button = xobj[0] if button_name := QUAD_BUTTON_TO_NAME[button]: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{button_name}", event_type="double_press", event_properties=None, ) return {} def obj560e( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: """Long button press""" if device_type not in ["KS1", "KS1BP"]: return {} button = xobj[0] if button_name := QUAD_BUTTON_TO_NAME[button]: device.fire_event( key=f"{str(EventDeviceKeys.BUTTON)}_{button_name}", event_type="long_press", event_properties=None, ) return {} # Dataobject dictionary # {dataObject_id: (converter} xiaomi_dataobject_dict = { 0x0003: obj0003, 0x0006: obj0006, 0x0007: obj0007, 0x0008: obj0008, 0x0010: obj0010, 0x000A: obj000a, 0x000B: obj000b, 0x000F: obj000f, 0x1001: obj1001, 0x1004: obj1004, 0x1005: obj1005, 0x1006: obj1006, 0x1007: obj1007, 0x1008: obj1008, 0x1009: obj1009, 0x1010: obj1010, 0x1012: obj1012, 0x1013: obj1013, 0x1014: obj1014, 0x1015: obj1015, 0x1017: obj1017, 0x1018: obj1018, 0x1019: obj1019, 0x100A: obj100a, 0x100D: obj100d, 0x100E: obj100e, 0x101B: obj101b, 0x2000: obj2000, 0x3003: obj3003, 0x4801: obj4801, 0x4802: obj4802, 0x4803: obj4803, 0x4804: obj4804, 0x4805: obj4805, 0x4806: obj4806, 0x4808: obj4808, 0x4818: obj4818, 0x484E: obj484e, 0x4851: obj4851, 0x4852: obj4852, 0x4A01: obj4a01, 0x4A08: obj4a08, 0x4A0C: obj4a0c, 0x4A0D: obj4a0d, 0x4A0E: obj4a0e, 0x4A0F: obj4a0f, 0x4A12: obj4a12, 0x4A13: obj4a13, 0x4A1A: obj4a1a, 0x4C01: obj4c01, 0x4C02: obj4c02, 0x4C03: obj4c03, 0x4C08: obj4c08, 0x4C14: obj4c14, 0x4E01: obj4e01, 0x4E1C: obj4e1c, 0x4E0C: obj4e0c, 0x4E0D: obj4e0d, 0x4E0E: obj4e0e, 0x5003: obj5003, 0x5414: obj5414, 0x560C: obj560c, 0x560D: obj560d, 0x560E: obj560e, } def decode_temps(packet_value: int) -> float: """Decode potential negative temperatures.""" # https://github.com/Thrilleratplay/XiaomiWatcher/issues/2 if packet_value & 0x800000: return float((packet_value ^ 0x800000) / -10000) return float(packet_value / 10000) def decode_temps_probes(packet_value: int) -> float: """Filter potential negative temperatures.""" if packet_value < 0: return 0.0 return float(packet_value / 100) class XiaomiBluetoothDeviceData(BluetoothData): """Data for Xiaomi BLE sensors.""" def __init__(self, bindkey: bytes | None = None) -> None: super().__init__() self.set_bindkey(bindkey) # Data that we know how to parse but don't yet map to the SensorData model. self.unhandled: dict[str, Any] = {} # The type of encryption to expect, based on flags in the bluetooth # frame. self.encryption_scheme = EncryptionScheme.NONE # If true then we have used the provided encryption key to decrypt at least # one payload. # If false then we have either not seen an encrypted payload, the key is wrong # or encryption is not in use self.bindkey_verified = False # If True then the decryption has failed or has not been verified yet. # If False then the decryption has succeeded. self.decryption_failed = True # If this is True, then we have not seen an advertisement with a payload # Until we see a payload, we can't tell if this device is encrypted or not self.pending = True # The last service_info we saw that had a payload # We keep this to help in reauth flows where we want to reprocess and old # value with a new bindkey. self.last_service_info: BluetoothServiceInfo | None = None # If this is True, the device is not sending advertisements # in a regular interval self.sleepy_device = False def set_bindkey(self, bindkey: bytes | None) -> None: """Set the bindkey.""" if bindkey: if len(bindkey) == 12: # MiBeacon v2/v3 bindkey (requires 4 additional (fixed) bytes) bindkey = b"".join( [bindkey[0:6], bytes.fromhex("8d3d3c97"), bindkey[6:]] ) elif len(bindkey) == 16: self.cipher: AESCCM | None = AESCCM(bindkey, tag_length=4) else: self.cipher = None self.bindkey = bindkey def supported(self, data: BluetoothServiceInfo) -> bool: if not super().supported(data): return False return True def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" _LOGGER.debug("Parsing Xiaomi BLE advertisement data: %s", service_info) for uuid, data in service_info.service_data.items(): if uuid == SERVICE_MIBEACON: if self._parse_xiaomi(service_info, service_info.name, data): self.last_service_info = service_info elif uuid == SERVICE_HHCCJCY10: if self._parse_hhcc(service_info, data): self.last_service_info = service_info elif uuid == SERVICE_SCALE1: if self._parse_scale_v1(service_info, data): self.last_service_info = service_info elif uuid == SERVICE_SCALE2: if self._parse_scale_v2(service_info, data): self.last_service_info = service_info def _parse_hhcc(self, service_info: BluetoothServiceInfo, data: bytes) -> bool: """Parser for Pink version of HHCCJCY10.""" if len(data) != 9: return False identifier = short_address(service_info.address) self.set_title(f"Plant Sensor {identifier} (HHCCJCY10)") self.set_device_name(f"Plant Sensor {identifier}") self.set_device_type("HHCCJCY10") self.set_device_manufacturer("HHCC Plant Technology Co. Ltd") xvalue_1 = data[0:3] (moist, temp) = struct.unpack(">BH", xvalue_1) self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp / 10) self.update_predefined_sensor(SensorLibrary.MOISTURE__PERCENTAGE, moist) xvalue_2 = data[3:6] (illu,) = struct.unpack(">i", b"\x00" + xvalue_2) self.update_predefined_sensor(SensorLibrary.LIGHT__LIGHT_LUX, illu) xvalue_3 = data[6:9] (batt, cond) = struct.unpack(">BH", xvalue_3) self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt) self.update_predefined_sensor(SensorLibrary.CONDUCTIVITY__CONDUCTIVITY, cond) return True def _parse_xiaomi( self, service_info: BluetoothServiceInfo, name: str, data: bytes ) -> bool: """Parser for Xiaomi sensors""" # check for adstruc length i = 5 # till Frame Counter msg_length = len(data) if msg_length < i: _LOGGER.debug("Invalid data length (initial check), adv: %s", data.hex()) return False mac_readable = service_info.address source_mac = bytes.fromhex(mac_readable.replace(":", "")) # extract frame control bits frctrl = data[0] + (data[1] << 8) frctrl_mesh = (frctrl >> 7) & 1 # mesh device frctrl_version = frctrl >> 12 # version frctrl_auth_mode = (frctrl >> 10) & 3 frctrl_solicited = (frctrl >> 9) & 1 frctrl_registered = (frctrl >> 8) & 1 frctrl_object_include = (frctrl >> 6) & 1 frctrl_capability_include = (frctrl >> 5) & 1 frctrl_mac_include = (frctrl >> 4) & 1 # check for MAC address in data frctrl_is_encrypted = (frctrl >> 3) & 1 # check for encryption being used frctrl_request_timing = frctrl & 1 # old version # Check that device is not of mesh type if frctrl_mesh != 0: _LOGGER.debug( "Device is a mesh type device, which is not supported. Data: %s", data.hex(), ) return False # Check that version is 2 or higher if frctrl_version < 2: _LOGGER.debug( "Device is using old data format, which is not supported. Data: %s", data.hex(), ) return False # Check that MAC in data is the same as the source MAC if frctrl_mac_include != 0: i += 6 if msg_length < i: _LOGGER.debug("Invalid data length (in MAC check), adv: %s", data.hex()) return False xiaomi_mac_reversed = data[5:11] xiaomi_mac = xiaomi_mac_reversed[::-1] if xiaomi_mac != source_mac: _LOGGER.debug( "MAC address doesn't match data frame. Expected: %s, Got: %s", to_mac(xiaomi_mac), to_mac(source_mac), ) return False else: xiaomi_mac = source_mac # determine the device type device_id = data[2] + (data[3] << 8) try: device = DEVICE_TYPES[device_id] except KeyError: _LOGGER.info( "BLE ADV from UNKNOWN Xiaomi device: MAC: %s, ADV: %s", source_mac, data.hex(), ) _LOGGER.debug("Unknown Xiaomi device found. Data: %s", data.hex()) return False device_type = device.model self.device_id = device_id self.device_type = device_type # set to True if the device is not sending regular BLE advertisements self.sleepy_device = device_type in SLEEPY_DEVICE_MODELS packet_id = data[4] sinfo = "MiVer: " + str(frctrl_version) sinfo += ", DevID: " + hex(device_id) + " : " + device_type sinfo += ", FnCnt: " + str(packet_id) if frctrl_request_timing != 0: sinfo += ", Request timing" if frctrl_registered != 0: sinfo += ", Registered and bound" else: sinfo += ", Not bound" if frctrl_solicited != 0: sinfo += ", Request APP to register and bind" if frctrl_auth_mode == 0: sinfo += ", Old version certification" elif frctrl_auth_mode == 1: sinfo += ", Safety certification" elif frctrl_auth_mode == 2: sinfo += ", Standard certification" # check for capability byte present if frctrl_capability_include != 0: i += 1 if msg_length < i: _LOGGER.debug( "Invalid data length (in capability check), adv: %s", data.hex() ) return False capability_types = data[i - 1] sinfo += ", Capability: " + hex(capability_types) if (capability_types & 0x20) != 0: i += 1 if msg_length < i: _LOGGER.debug( "Invalid data length (in capability type check), adv: %s", data.hex(), ) return False capability_io = data[i - 1] sinfo += ", IO: " + hex(capability_io) identifier = short_address(service_info.address) self.set_title(f"{device.name} {identifier} ({device.model})") self.set_device_name(f"{device.name} {identifier}") self.set_device_type(device.model) self.set_device_manufacturer(device.manufacturer) # check that data contains object if frctrl_object_include == 0: # data does not contain Object _LOGGER.debug("Advertisement doesn't contain payload, adv: %s", data.hex()) return False self.pending = False # check for encryption if frctrl_is_encrypted != 0: sinfo += ", Encryption" firmware = "Xiaomi (MiBeacon V" + str(frctrl_version) + " encrypted)" if frctrl_version <= 3: self.encryption_scheme = EncryptionScheme.MIBEACON_LEGACY payload = self._decrypt_mibeacon_legacy(data, i, xiaomi_mac) else: self.encryption_scheme = EncryptionScheme.MIBEACON_4_5 payload = self._decrypt_mibeacon_v4_v5(data, i, xiaomi_mac) else: # No encryption # check minimum advertisement length with data firmware = "Xiaomi (MiBeacon V" + str(frctrl_version) + ")" sinfo += ", No encryption" if msg_length < i + 3: _LOGGER.debug( "Invalid data length (in non-encrypted data), adv: %s", data.hex(), ) return False payload = data[i:] self.set_device_sw_version(firmware) if payload is not None: sinfo += ", Object data: " + payload.hex() # loop through parse_xiaomi payload payload_start = 0 payload_length = len(payload) # assume that the data may have several values of different types while payload_length >= payload_start + 3: obj_typecode = payload[payload_start] + ( payload[payload_start + 1] << 8 ) obj_length = payload[payload_start + 2] next_start = payload_start + 3 + obj_length if payload_length < next_start: # The payload segments are corrupted - if this is legacy encryption # then the key is probably just wrong # V4/V5 encryption has an authentication tag, so we don't apply the # same restriction there. if self.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY: if self.decryption_failed is True: # we only ask for reautentification # till the decryption has failed twice. self.bindkey_verified = False else: self.decryption_failed = True _LOGGER.debug( "Invalid payload data length, payload: %s", payload.hex() ) break this_start = payload_start + 3 dobject = payload[this_start:next_start] if ( dobject and obj_length != 0 or hex(obj_typecode) in OBJECTS_DEVICE_TYPE ): resfunc = xiaomi_dataobject_dict.get(obj_typecode, None) if resfunc: self.unhandled.update(resfunc(dobject, self, device_type)) else: _LOGGER.info( "%s, UNKNOWN dataobject in payload! Adv: %s", sinfo, data.hex(), ) payload_start = next_start return True def _parse_scale_v1(self, service_info: BluetoothServiceInfo, data: bytes) -> bool: if len(data) != 10: return False uuid16 = (data[3] << 8) | data[2] identifier = short_address(service_info.address) self.device_id = uuid16 self.set_title(f"Mi Smart Scale ({identifier})") self.set_device_name(f"Mi Smart Scale ({identifier})") self.set_device_type("XMTZC01HM/XMTZC04HM") self.set_device_manufacturer("Xiaomi") self.pending = False self.sleepy_device = True control_byte = data[0] mass = float(int.from_bytes(data[1:3], byteorder="little")) mass_in_pounds = bool(int(control_byte & (1 << 0))) mass_in_catty = bool(int(control_byte & (1 << 4))) mass_in_kilograms = not mass_in_catty and not mass_in_pounds mass_stabilized = bool(int(control_byte & (1 << 5))) mass_removed = bool(int(control_byte & (1 << 7))) if mass_in_kilograms: # sensor advertises kg * 200 mass /= 200 elif mass_in_pounds: # sensor advertises lbs * 100, conversion to kg (1 lbs = 0.45359237 kg) mass *= 0.0045359237 else: # sensor advertises catty * 100, conversion to kg (1 catty = 0.5 kg) mass *= 0.005 self.update_predefined_sensor( SensorLibrary.MASS_NON_STABILIZED__MASS_KILOGRAMS, mass ) if mass_stabilized and not mass_removed: self.update_predefined_sensor(SensorLibrary.MASS__MASS_KILOGRAMS, mass) return True def _parse_scale_v2(self, service_info: BluetoothServiceInfo, data: bytes) -> bool: if len(data) != 13: return False uuid16 = (data[3] << 8) | data[2] identifier = short_address(service_info.address) self.device_id = uuid16 self.set_title(f"Mi Body Composition Scale ({identifier})") self.set_device_name(f"Mi Body Composition Scale ({identifier})") self.set_device_type("XMTZC02HM/XMTZC05HM/NUN4049CN") self.set_device_manufacturer("Xiaomi") self.pending = False self.sleepy_device = True control_bytes = data[:2] # skip bytes containing date and time impedance = int.from_bytes(data[9:11], byteorder="little") mass = float(int.from_bytes(data[11:13], byteorder="little")) # Decode control bytes control_flags = "".join([bin(byte)[2:].zfill(8) for byte in control_bytes]) mass_in_pounds = bool(int(control_flags[7])) mass_in_catty = bool(int(control_flags[9])) mass_in_kilograms = not mass_in_catty and not mass_in_pounds mass_stabilized = bool(int(control_flags[10])) mass_removed = bool(int(control_flags[8])) impedance_stabilized = bool(int(control_flags[14])) if mass_in_kilograms: # sensor advertises kg * 200 mass /= 200 elif mass_in_pounds: # sensor advertises lbs * 100, conversion to kg (1 lbs = 0.45359237 kg) mass *= 0.0045359237 else: # sensor advertises catty * 100, conversion to kg (1 catty = 0.5 kg) mass *= 0.005 self.update_predefined_sensor( SensorLibrary.MASS_NON_STABILIZED__MASS_KILOGRAMS, mass ) if mass_stabilized and not mass_removed: self.update_predefined_sensor(SensorLibrary.MASS__MASS_KILOGRAMS, mass) if impedance_stabilized: self.update_predefined_sensor(SensorLibrary.IMPEDANCE__OHM, impedance) return True def _decrypt_mibeacon_v4_v5( self, data: bytes, i: int, xiaomi_mac: bytes ) -> bytes | None: """decrypt MiBeacon v4/v5 encrypted advertisements""" # check for minimum length of encrypted advertisement if len(data) < i + 9: _LOGGER.debug("Invalid data length (for decryption), adv: %s", data.hex()) return None if not self.bindkey: self.bindkey_verified = False _LOGGER.debug("Encryption key not set and adv is encrypted") return None if not self.bindkey or len(self.bindkey) != 16: self.bindkey_verified = False _LOGGER.error("Encryption key should be 16 bytes (32 characters) long") return None nonce = b"".join([xiaomi_mac[::-1], data[2:5], data[-7:-4]]) associated_data = b"\x11" mic = data[-4:] encrypted_payload = data[i:-7] assert self.cipher is not None # nosec # decrypt the data try: decrypted_payload = self.cipher.decrypt( nonce, encrypted_payload + mic, associated_data ) except InvalidTag as error: if self.decryption_failed is True: # we only ask for reautentification till # the decryption has failed twice. self.bindkey_verified = False else: self.decryption_failed = True _LOGGER.warning("Decryption failed: %s", error) _LOGGER.debug("mic: %s", mic.hex()) _LOGGER.debug("nonce: %s", nonce.hex()) _LOGGER.debug("encrypted payload: %s", encrypted_payload.hex()) return None if decrypted_payload is None: self.bindkey_verified = False _LOGGER.error( "Decryption failed for %s, decrypted payload is None", to_mac(xiaomi_mac), ) return None self.decryption_failed = False self.bindkey_verified = True return decrypted_payload def _decrypt_mibeacon_legacy( self, data: bytes, i: int, xiaomi_mac: bytes ) -> bytes | None: """decrypt MiBeacon v2/v3 encrypted advertisements""" # check for minimum length of encrypted advertisement if len(data) < i + 7: _LOGGER.debug("Invalid data length (for decryption), adv: %s", data.hex()) return None if not self.bindkey: self.bindkey_verified = False _LOGGER.debug("Encryption key not set and adv is encrypted") return None if len(self.bindkey) != 16: self.bindkey_verified = False _LOGGER.error("Encryption key should be 12 bytes (24 characters) long") return None nonce = b"".join([data[0:5], data[-4:-1], xiaomi_mac[::-1][:-1]]) encrypted_payload = data[i:-4] # cryptography can't decrypt a message without authentication # so we have to use Cryptodome associated_data = b"\x11" cipher = AES.new(self.bindkey, AES.MODE_CCM, nonce=nonce, mac_len=4) cipher.update(associated_data) assert cipher is not None # nosec # decrypt the data # note that V2/V3 encryption will often pass the decryption process with a # wrong encryption key, resulting in useless data, and we won't be able # to verify this, as V2/V3 encryption does not use a tag to verify # the decrypted data. This will be filtered as wrong data length # during the conversion of the payload to sensor data. try: decrypted_payload = cipher.decrypt(encrypted_payload) except ValueError as error: self.bindkey_verified = False _LOGGER.warning("Decryption failed: %s", error) _LOGGER.debug("nonce: %s", nonce.hex()) _LOGGER.debug("encrypted payload: %s", encrypted_payload.hex()) return None if decrypted_payload is None: self.bindkey_verified = False _LOGGER.warning( "Decryption failed for %s, decrypted payload is None", to_mac(xiaomi_mac), ) return None self.bindkey_verified = True return decrypted_payload def poll_needed( self, service_info: BluetoothServiceInfo, last_poll: float | None ) -> bool: """ This is called every time we get a service_info for a device. It means the device is working and online. If 24 hours has passed, it may be a good time to poll the device. """ if self.pending: # Never need to poll if we are pending as we don't even know what # kind of device we are return False if self.device_id not in [0x03BC, 0x0098]: return False return not last_poll or last_poll > TIMEOUT_1DAY async def async_poll(self, ble_device: BLEDevice) -> SensorUpdate: """ Poll the device to retrieve any values we can't get from passive listening. """ if self.device_id in [0x03BC, 0x0098]: client = await establish_connection( BleakClient, ble_device, ble_device.address ) try: battery_char = client.services.get_characteristic( CHARACTERISTIC_BATTERY ) payload = await client.read_gatt_char(battery_char) finally: await client.disconnect() self.set_device_sw_version(payload[2:].decode("utf-8")) self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, payload[0]) return self._finish_update() Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/locks.py0000775000175000017500000000567114772435047026167 0ustar billchenchinabillchenchina"""Constants for Xiaomi Locks.""" from enum import Enum class BleLockMethod(Enum): """Methods for opening and closing locks.""" BLUETOOTH = "bluetooth" PASSWORD = "password" # nosec bandit B105 BIOMETRICS = "biometrics" KEY_METHOD = "key_method" TURNTABLE = "turntable_method" NFC = "nfc_method" ONE_TIME_PASSWORD = "one_time_password" # nosec bandit B105 TWO_STEP_VERIFICATION = "two_step_verification" HOMEKIT = "homekit" COERCION = "coercion_method" MANUAL = "manual" AUTOMATIC = "automatic" ABNORMAL = "abnormal" # Definition of lock messages BLE_LOCK_ERROR = { 0xC0DE0000: "frequent_unlocking_with_incorrect_password", 0xC0DE0001: "frequent_unlocking_with_wrong_fingerprints", 0xC0DE0002: "operation_timeout_password_input_timeout", 0xC0DE0003: "lock_picking", 0xC0DE0004: "reset_button_is_pressed", 0xC0DE0005: "the_wrong_key_is_frequently_unlocked", 0xC0DE0006: "foreign_body_in_the_keyhole", 0xC0DE0007: "the_key_has_not_been_taken_out", 0xC0DE0008: "error_nfc_frequently_unlocks", 0xC0DE0009: "timeout_is_not_locked_as_required", 0xC0DE000A: "failure_to_unlock_frequently_in_multiple_ways", 0xC0DE000B: "unlocking_the_face_frequently_fails", 0xC0DE000C: "failure_to_unlock_the_vein_frequently", 0xC0DE000D: "hijacking_alarm", 0xC0DE000E: "unlock_inside_the_door_after_arming", 0xC0DE000F: "palmprints_frequently_fail_to_unlock", 0xC0DE0010: "the_safe_was_moved", 0xC0DE1000: "the_battery_level_is_less_than_10_percent", 0xC0DE1001: "the_battery_level_is_less_than_5_percent", 0xC0DE1002: "the_fingerprint_sensor_is_abnormal", 0xC0DE1003: "the_accessory_battery_is_low", 0xC0DE1004: "mechanical_failure", 0xC0DE1005: "the_lock_sensor_is_faulty", } BLE_LOCK_ACTION: dict[int, tuple[bool, str, str]] = { 0b0000: (True, "lock", "unlock_outside_the_door"), 0b0001: (False, "lock", "locked"), 0b0010: (False, "antilock", "turn_on_antilock"), 0b0011: (True, "antilock", "release_the_antilock"), 0b0100: (True, "lock", "unlock_inside_the_door"), 0b0101: (False, "lock", "lock_inside_the_door"), 0b0110: (False, "childlock", "turn_on_child_lock"), 0b0111: (True, "childlock", "turn_off_child_lock"), 0b1000: (False, "lock", "lock_outside_the_door"), 0b1111: (True, "lock", "abnormal"), } BLE_LOCK_METHOD: dict[int, BleLockMethod] = { 0b0000: BleLockMethod.BLUETOOTH, 0b0001: BleLockMethod.PASSWORD, 0b0010: BleLockMethod.BIOMETRICS, 0b0011: BleLockMethod.KEY_METHOD, 0b0100: BleLockMethod.TURNTABLE, 0b0101: BleLockMethod.NFC, 0b0110: BleLockMethod.ONE_TIME_PASSWORD, 0b0111: BleLockMethod.TWO_STEP_VERIFICATION, 0b1001: BleLockMethod.HOMEKIT, 0b1000: BleLockMethod.COERCION, 0b1010: BleLockMethod.MANUAL, 0b1011: BleLockMethod.AUTOMATIC, 0b1111: BleLockMethod.ABNORMAL, } Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/const.py0000664000175000017500000000454014772435047026171 0ustar billchenchinabillchenchina"""Constants for Xiaomi BLE advertisements.""" from enum import Enum from sensor_state_data import BaseDeviceClass TIMEOUT_1DAY = 86400 SERVICE_MIBEACON = "0000fe95-0000-1000-8000-00805f9b34fb" SERVICE_HHCCJCY10 = "0000fd50-0000-1000-8000-00805f9b34fb" SERVICE_SCALE1 = "0000181d-0000-1000-8000-00805f9b34fb" SERVICE_SCALE2 = "0000181b-0000-1000-8000-00805f9b34fb" # This characteristic contains the current battery level for a HHCCJCY01 # as well as the firmware version CHARACTERISTIC_BATTERY = "00001a02-0000-1000-8000-00805f9b34fb" class EncryptionScheme(Enum): """Encryption Schemes for Xiaomi MiBeacon.""" # No encryption is needed to use this device NONE = "none" # 12 byte encryption key expected MIBEACON_LEGACY = "mibeacon_legacy" # 16 byte encryption key expected MIBEACON_4_5 = "mibeacon_4_5" class ExtendedBinarySensorDeviceClass(BaseDeviceClass): """Device class for additional binary sensors (compared to sensor-state-data).""" # On means armed (away), Off means disarmed ARMED = "armed" # On means door left open, Off means door closed DEVICE_FORCIBLY_REMOVED = "device_forcibly_removed" # On means door left open, Off means door closed DOOR_LEFT_OPEN = "door_left_open" # On means door stuck, Off means clear DOOR_STUCK = "door_stuck" # On means fingerprint Ok, Off means fingerprint Not Ok FINGERPRINT = "fingerprint" # On means door someone knocking on the door, Off means no knocking KNOCK_ON_THE_DOOR = "knock_on_the_door" # On means door pried, Off means door not pried PRY_THE_DOOR = "pry_the_door" # On means toothbrush On, Off means toothbrush Off TOOTHBRUSH = "toothbrush" # On means antilock turned On, Off means antilOck turned Off ANTILOCK = "antilock" # On means childlock Turned On, Off means childlock turned Off CHILDLOCK = "childlock" class ExtendedSensorDeviceClass(BaseDeviceClass): """Device class for additional sensors (compared to sensor-state-data).""" # Consumable CONSUMABLE = "consumable" # Toothbrush counter COUNTER = "counter" # Key id KEY_ID = "key_id" # Lock method LOCK_METHOD = "lock_method" # Toothbrush score SCORE = "score" # Has-Someone-Duration DURATION_DETECTED = "duration_detected" # No-One-Duration DURATION_CLEARED = "duration_cleared" Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/devices.py0000664000175000017500000001455214772435047026471 0ustar billchenchinabillchenchinaimport dataclasses @dataclasses.dataclass class DeviceEntry: name: str model: str manufacturer: str = "Xiaomi" DEVICE_TYPES: dict[int, DeviceEntry] = { 0x0C3C: DeviceEntry( name="Alarm Clock", model="CGC1", ), 0x0576: DeviceEntry( name="3-in-1 Alarm Clock", model="CGD1", ), 0x066F: DeviceEntry( name="Temperature/Humidity Sensor", model="CGDK2", ), 0x0347: DeviceEntry( name="Temperature/Humidity Sensor", model="CGG1", ), 0x0B48: DeviceEntry( name="Temperature/Humidity Sensor", model="CGG1-ENCRYPTED", ), 0x03D6: DeviceEntry( name="Door/Window Sensor", model="CGH1", ), 0x0A83: DeviceEntry( name="Motion/Light Sensor", model="CGPR1", ), 0x03BC: DeviceEntry( name="Grow Care Garden", model="GCLS002", ), 0x0098: DeviceEntry( name="Plant Sensor", model="HHCCJCY01", ), 0x015D: DeviceEntry( name="Smart Flower Pot", model="HHCCPOT002", ), 0x02DF: DeviceEntry( name="Formaldehyde Sensor", model="JQJCY01YM", ), 0x0997: DeviceEntry( name="Smoke Detector", model="JTYJGD03MI", ), 0x1568: DeviceEntry( name="Switch (single button)", model="K9B-1BTN", ), 0x1569: DeviceEntry( name="Switch (double button)", model="K9B-2BTN", ), 0x0DFD: DeviceEntry( name="Switch (triple button)", model="K9B-3BTN", ), 0x1C10: DeviceEntry( name="Switch (single button)", model="K9BB-1BTN", ), 0x3A61: DeviceEntry( name="Switch (quadruple button)", model="KS1", ), 0x3E17: DeviceEntry( name="Switch (quadruple button)", model="KS1BP", ), 0x1889: DeviceEntry( name="Door/Window Sensor", model="MS1BB(MI)", ), 0x2AEB: DeviceEntry( name="Motion Sensor", model="HS1BB(MI)", ), 0x3F0F: DeviceEntry(name="Flood and Rain Sensor", model="RS1BB(MI)"), 0x01AA: DeviceEntry( name="Temperature/Humidity Sensor", model="LYWSDCGQ", ), 0x045B: DeviceEntry( name="Temperature/Humidity Sensor", model="LYWSD02", ), 0x16E4: DeviceEntry( name="Temperature/Humidity Sensor", model="LYWSD02MMC", ), 0x2542: DeviceEntry( name="Temperature/Humidity Sensor", model="LYWSD02MMC", ), 0x055B: DeviceEntry( name="Temperature/Humidity Sensor", model="LYWSD03MMC", ), 0x2832: DeviceEntry( name="Temperature/Humidity Sensor", model="MJWSD05MMC", ), 0x55B5: DeviceEntry( name="Temperature/Humidity Sensor", model="MJWSD06MMC", ), 0x098B: DeviceEntry( name="Door/Window Sensor", model="MCCGQ02HL", ), 0x06D3: DeviceEntry( name="Alarm Clock", model="MHO-C303", ), 0x0387: DeviceEntry( name="Temperature/Humidity Sensor", model="MHO-C401", ), 0x07F6: DeviceEntry( name="Nightlight", model="MJYD02YL", ), 0x04E9: DeviceEntry( name="Door Lock", model="MJZNMSQ01YD", ), 0x00DB: DeviceEntry( name="Baby Thermometer", model="MMC-T201-1", ), 0x0391: DeviceEntry( name="Body Thermometer", model="MMC-W505", ), 0x03DD: DeviceEntry( name="Nightlight", model="MUE4094RT", ), 0x0489: DeviceEntry( name="Smart Toothbrush", model="M1S-T500", ), 0x0806: DeviceEntry( name="Smart Toothbrush", model="T700", ), 0x1790: DeviceEntry( name="Smart Toothbrush", model="T700", ), 0x0A8D: DeviceEntry( name="Motion Sensor", model="RTCGQ02LM", ), 0x3531: DeviceEntry( name="Motion Sensor", model="XMPIRO2SXS", ), 0x4C60: DeviceEntry( name="Motion Sensor", model="XMPIRO2GSXS", ), 0x4683: DeviceEntry( name="Occupancy Sensor", model="XMOSB01XS", ), 0x0863: DeviceEntry( name="Flood Detector", model="SJWS01LM", ), 0x045C: DeviceEntry( name="Smart Kettle", model="V-SK152", ), 0x040A: DeviceEntry( name="Mosquito Repellent", model="WX08ZM", ), 0x04E1: DeviceEntry( name="Magic Cube", model="XMMF01JQD", ), 0x1203: DeviceEntry( name="Thermometer", model="XMWSDJ04MMC", ), 0x1949: DeviceEntry( name="Switch (double button)", model="XMWXKG01YL", ), 0x2387: DeviceEntry( name="Button", model="XMWXKG01LM", ), 0x098C: DeviceEntry( name="Door Lock", model="XMZNMST02YD", ), 0x0784: DeviceEntry( name="Door Lock", model="XMZNMS04LM", ), 0x0E39: DeviceEntry( name="Door Lock", model="XMZNMS08LM", ), 0x07BF: DeviceEntry( name="Wireless Switch", model="YLAI003", ), 0x38BB: DeviceEntry( name="Wireless Switch", model="PTX_YK1_QMIMB", ), 0x0153: DeviceEntry( name="Remote Control", model="YLYK01YL", ), 0x068E: DeviceEntry( name="Fan Remote Control", model="YLYK01YL-FANCL", ), 0x04E6: DeviceEntry( name="Ventilator Fan Remote Control", model="YLYK01YL-VENFAN", ), 0x03BF: DeviceEntry( name="Bathroom Heater Remote", model="YLYB01YL-BHFRC", ), 0x03B6: DeviceEntry( name="Dimmer Switch", model="YLKG07YL/YLKG08YL", ), 0x0083: DeviceEntry( name="Smart Kettle", model="YM-K1501", ), 0x0113: DeviceEntry( name="Smart Kettle", model="YM-K1501EU", ), 0x069E: DeviceEntry( name="Door Lock", model="ZNMS16LM", ), 0x069F: DeviceEntry( name="Door Lock", model="ZNMS17LM", ), 0x0380: DeviceEntry( name="Door Lock", model="DSL-C08", ), 0x11C2: DeviceEntry( name="Door Lock", model="Lockin-SV40", ), 0x0DE7: DeviceEntry( name="Odor Eliminator", model="SU001-T", ), } SLEEPY_DEVICE_MODELS = { "CGH1", "JTYJGD03MI", "MCCGQ02HL", "RTCGQ02LM", "MMC-W505", "RS1BB(MI)", "XMOSB01XS", } Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/events.py0000664000175000017500000000070114772435047026342 0ustar billchenchinabillchenchina"""Event constants for xiaomi-ble.""" from __future__ import annotations from sensor_state_data.enum import StrEnum class EventDeviceKeys(StrEnum): """Keys for devices that send events.""" # Button BUTTON = "button" # Cube CUBE = "cube" # Dimmer DIMMER = "dimmer" # Error ERROR = "error" # Fingerprint FINGERPRINT = "fingerprint" # Motion MOTION = "motion" # Lock LOCK = "lock" Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/cloud.py0000664000175000017500000004037714772435047026161 0ustar billchenchinabillchenchinaimport base64 import hashlib import hmac import logging import os import random import time from dataclasses import dataclass from typing import Any import aiohttp import orjson from Cryptodome.Cipher import ARC4 from yarl import URL SERVERS = ["cn", "de", "us", "ru", "tw", "sg", "in", "i2"] _LOGGER = logging.getLogger(__name__) # Adapted from PiotrMachowski's Xiaomi-cloud-tokens-extractor # MIT License # # Copyright (c) 2020 Piotr Machowski # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. LOGIN_URL = URL("https://account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true") LOGIN_URL2 = URL("https://account.xiaomi.com/pass/serviceLoginAuth2") @dataclass class XiaomiCloudBLEDevice: name: str mac: str bindkey: str class XiaomiCloudException(Exception): """Raised when an error occurs during Xiaomi Cloud API communication.""" class XiaomiCloudInvalidAuthenticationException(XiaomiCloudException): """Raised when an invalid authentication method is provided.""" class XiaomiCloudInvalidUsernameException(XiaomiCloudInvalidAuthenticationException): """Raised when an invalid username is provided.""" class XiaomiCloudInvalidPasswordException(XiaomiCloudInvalidAuthenticationException): """Raised when an invalid password is provided.""" class XiaomiCloudTwoFactorAuthenticationException( XiaomiCloudInvalidAuthenticationException ): """Raised when two factor authentication is required.""" def __init__(self, message: str, url: str) -> None: """Initialize the exception.""" super().__init__(message) self.url = url class XiaomiCloudConnector: """Encapsulates Xiaomi Cloud API.""" def __init__( self, username: str, password: str, session: aiohttp.ClientSession ) -> None: """Initialize the Xiaomi Cloud API.""" self._username = username self._password = password self._agent = self.generate_agent() self._device_id = self.generate_device_id() self._session = session self._sign: str | None = None self._ssecurity: str | None = None self.userId: str | None = None self._cUserId: str | None = None self._passToken: str | None = None self._location: str | None = None self._code: str | None = None self._serviceToken: str | None = None async def _login_step_1(self) -> bool: headers = { "User-Agent": self._agent, "Content-Type": "application/x-www-form-urlencoded", } cookies = {**self._cookies, "userId": self._username} response = await self._session.get(LOGIN_URL, headers=headers, cookies=cookies) valid = response.status == 200 and "_sign" in self.to_json( await response.text() ) if valid: self._sign = self.to_json(await response.text())["_sign"] return valid async def _login_step_2(self) -> bool: url = LOGIN_URL2 headers = { "User-Agent": self._agent, "Content-Type": "application/x-www-form-urlencoded", } fields = { "sid": "xiaomiio", "hash": hashlib.md5(str.encode(self._password)).hexdigest().upper(), "callback": "https://sts.api.io.mi.com/sts", "qs": "%3Fsid%3Dxiaomiio%26_json%3Dtrue", "user": self._username, "_sign": self._sign, "_json": "true", } response = await self._session.post( url, headers=headers, params=fields, cookies=self._cookies ) valid = response is not None and response.status == 200 if valid: json_resp = self.to_json(await response.text()) valid = "ssecurity" in json_resp and len(str(json_resp["ssecurity"])) > 4 if valid: self._ssecurity = json_resp["ssecurity"] self.userId = json_resp["userId"] self._cUserId = json_resp["cUserId"] self._passToken = json_resp["passToken"] self._location = json_resp["location"] self._code = json_resp["code"] elif "notificationUrl" in json_resp: raise XiaomiCloudTwoFactorAuthenticationException( "Two factor authentication required.", json_resp["notificationUrl"] ) return valid async def _login_step_3(self) -> bool: headers = { "User-Agent": self._agent, "Content-Type": "application/x-www-form-urlencoded", } response = await self._session.get( self._location, headers=headers, cookies=self._cookies ) if response.status == 200: self._serviceToken = response.cookies.get("serviceToken").value return response.status == 200 async def login(self) -> bool: self._cookies = { "sdkVersion": "accountsdk-18.8.15", "deviceId": self._device_id, } if not await self._login_step_1(): raise XiaomiCloudInvalidUsernameException("Invalid username.") if not await self._login_step_2(): raise XiaomiCloudInvalidPasswordException("Invalid password.") if not await self._login_step_3(): raise XiaomiCloudException("Unable to get service token.") return True async def get_homes(self, country: str) -> dict[str, Any] | None: url = self.get_api_url(country) + "/v2/homeroom/gethome" params = { "data": '{"fg": true, "fetch_share": true, "fetch_share_dev": true, "limit": 300, "app_ver": 7}' # noqa } return await self.execute_api_call_encrypted(url, params) async def get_devices( self, country: str, home_id: str, owner_id: str ) -> dict[str, Any] | None: url = self.get_api_url(country) + "/v2/home/home_device_list" params = { "data": '{"home_owner": ' + str(owner_id) + ',"home_id": ' + str(home_id) + ', "limit": 200, "get_split_device": true, "support_smart_home": true}' } return await self.execute_api_call_encrypted(url, params) async def get_dev_cnt(self, country: str) -> dict[str, Any] | None: url = self.get_api_url(country) + "/v2/user/get_device_cnt" params = {"data": '{ "fetch_own": true, "fetch_share": true}'} return await self.execute_api_call_encrypted(url, params) async def get_beaconkey(self, country: str, did: str) -> dict[str, Any] | None: url = self.get_api_url(country) + "/v2/device/blt_get_beaconkey" params = {"data": '{"did":"' + did + '","pdid":1}'} return await self.execute_api_call_encrypted(url, params) async def execute_api_call_encrypted( self, url: str, params: dict[str, Any] ) -> dict[str, Any] | None: headers = { "Accept-Encoding": "identity", "User-Agent": self._agent, "Content-Type": "application/x-www-form-urlencoded", "x-xiaomi-protocal-flag-cli": "PROTOCAL-HTTP2", "MIOT-ENCRYPT-ALGORITHM": "ENCRYPT-RC4", } cookies = { "userId": str(self.userId), "yetAnotherServiceToken": str(self._serviceToken), "serviceToken": str(self._serviceToken), "locale": "en_GB", "timezone": "GMT+02:00", "is_daylight": "1", "dst_offset": "3600000", "channel": "MI_APP_STORE", **self._cookies, } millis = round(time.time() * 1000) nonce = self.generate_nonce(millis) signed_nonce = self.signed_nonce(nonce) assert self._ssecurity is not None fields = self.generate_enc_params( url, "POST", signed_nonce, nonce, params, self._ssecurity ) response = await self._session.post( url, headers=headers, cookies=cookies, params=fields ) if response.status == 200: decoded = self.decrypt_rc4( self.signed_nonce(fields["_nonce"]), await response.text() ) return orjson.loads(decoded) if response.status > 400 and response.status < 500: raise XiaomiCloudInvalidAuthenticationException("Authentication failed") return None @staticmethod def get_api_url(country: str) -> str: return ( "https://" + ("" if country == "cn" else (country + ".")) + "api.io.mi.com/app" ) def signed_nonce(self, nonce: str) -> str: assert self._ssecurity is not None hash_object = hashlib.sha256( base64.b64decode(self._ssecurity) + base64.b64decode(nonce) ) return base64.b64encode(hash_object.digest()).decode("utf-8") @staticmethod def signed_nonce_sec(nonce: str, ssecurity: str) -> str: hash_object = hashlib.sha256( base64.b64decode(ssecurity) + base64.b64decode(nonce) ) return base64.b64encode(hash_object.digest()).decode("utf-8") @staticmethod def generate_nonce(millis: int) -> str: nonce_bytes = os.urandom(8) + (int(millis / 60000)).to_bytes(4, byteorder="big") return base64.b64encode(nonce_bytes).decode() @staticmethod def generate_agent() -> str: agent_id = "".join( map(lambda i: chr(i), [random.randint(65, 69) for _ in range(13)]) ) return f"Android-7.1.1-1.0.0-ONEPLUS A3010-136-{agent_id} APP/xiaomi.smarthome APPV/62830" # noqa @staticmethod def generate_device_id() -> str: return "".join( map(lambda i: chr(i), [random.randint(97, 122) for _ in range(6)]) ) @staticmethod def generate_signature( url: str, signed_nonce: str, nonce: str, params: dict[str, Any] ) -> str: signature_params: list[str] = [url.split("com")[1], signed_nonce, nonce] for k, v in params.items(): signature_params.append(f"{k}={v}") signature_string = "&".join(signature_params) signature = hmac.new( base64.b64decode(signed_nonce), msg=signature_string.encode(), digestmod=hashlib.sha256, ) return base64.b64encode(signature.digest()).decode() @staticmethod def generate_enc_signature( url: str, method: str, signed_nonce: str, params: dict[str, Any] ) -> str: signature_params: list[str] = [ str(method).upper(), url.split("com")[1].replace("/app/", "/"), ] for k, v in params.items(): signature_params.append(f"{k}={v}") signature_params.append(signed_nonce) signature_string = "&".join(signature_params) return base64.b64encode( hashlib.sha1(signature_string.encode("utf-8")).digest() ).decode() @staticmethod def generate_enc_params( url: str, method: str, signed_nonce: str, nonce: str, params: dict[str, Any], ssecurity: str, ) -> dict[str, Any]: params["rc4_hash__"] = XiaomiCloudConnector.generate_enc_signature( url, method, signed_nonce, params ) for k, v in params.items(): params[k] = XiaomiCloudConnector.encrypt_rc4(signed_nonce, v) params.update( { "signature": XiaomiCloudConnector.generate_enc_signature( url, method, signed_nonce, params ), "ssecurity": ssecurity, "_nonce": nonce, } ) return params @staticmethod def to_json(response_text: str) -> dict[str, Any]: """Convert a response to a JSON object.""" return orjson.loads(response_text.replace("&&&START&&&", "")) @staticmethod def encrypt_rc4(password: str, payload: str) -> str: """Encrypt a piece of data.""" r = ARC4.new(base64.b64decode(password)) r.encrypt(bytes(1024)) return base64.b64encode(r.encrypt(payload.encode())).decode() @staticmethod def decrypt_rc4(password: str, payload: str) -> str: """Decrypt a piece of data.""" r = ARC4.new(base64.b64decode(password)) r.encrypt(bytes(1024)) return r.encrypt(base64.b64decode(payload)) class XiaomiCloudTokenFetch: def __init__( self, username: str, password: str, session: aiohttp.ClientSession ) -> None: """Initialize the Xiaomi Cloud API.""" self._username = username self._password = password self._session = session async def get_device_info( self, mac: str, servers: list[str] = SERVERS ) -> XiaomiCloudBLEDevice | None: """Get the token for a given MAC address.""" formatted_mac = format_mac_upper(mac) connector = XiaomiCloudConnector(self._username, self._password, self._session) await connector.login() assert connector.userId is not None homes: dict[str, str] = {} for server in servers: home_info = await connector.get_homes(server) if home_info is not None: for h in home_info["result"]["homelist"]: homes[h["id"]] = connector.userId dev_cnt = await connector.get_dev_cnt(server) if dev_cnt is not None: for h in dev_cnt["result"]["share"]["share_family"]: homes[h["home_id"]] = h["home_owner"] for home_id, owner_id in homes.items(): devices = await connector.get_devices(server, home_id, owner_id) if ( devices is None or not devices["result"] or not devices["result"]["device_info"] ): continue device_info: list[dict[str, Any]] = devices["result"]["device_info"] for device in device_info: if ( device["mac"] != formatted_mac or "did" not in device or "blt" not in device["did"] ): continue key_result = await connector.get_beaconkey(server, device["did"]) if ( key_result and (result := key_result.get("result")) and (beacon_key := result.get("beaconkey")) ): _LOGGER.debug( "Found beacon key for %s: %s (%s)", formatted_mac, beacon_key, device, ) return XiaomiCloudBLEDevice( device["name"], device["mac"], beacon_key ) return None def format_mac_upper(mac: str) -> str: """Format the mac address string to be upper case.""" to_test = mac if len(to_test) == 17 and to_test.count(":") == 5: return to_test.upper() if len(to_test) == 17 and to_test.count("-") == 5: to_test = to_test.replace("-", "") elif len(to_test) == 14 and to_test.count(".") == 2: to_test = to_test.replace(".", "") if len(to_test) == 12: # no : included return ":".join( to_test.upper()[i : i + 2] for i in range(0, 12, 2) # noqa: E203 ) # Not sure how formatted, return original return mac Bluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/py.typed0000664000175000017500000000000014772435047026153 0ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/src/xiaomi_ble/__init__.py0000664000175000017500000000254214772435047026602 0ustar billchenchinabillchenchina"""Parser for Xiaomi BLE advertisements. This file is shamlessly copied from the following repository: https://github.com/Ernst79/bleparser/blob/c42ae922e1abed2720c7fac993777e1bd59c0c93/package/bleparser/Xiaomi.py MIT License applies. """ from __future__ import annotations from sensor_state_data import ( DeviceClass, DeviceKey, SensorDescription, SensorDeviceInfo, SensorUpdate, SensorValue, Units, ) from .cloud import ( XiaomiCloudBLEDevice, XiaomiCloudException, XiaomiCloudInvalidAuthenticationException, XiaomiCloudInvalidPasswordException, XiaomiCloudInvalidUsernameException, XiaomiCloudTokenFetch, XiaomiCloudTwoFactorAuthenticationException, ) from .devices import SLEEPY_DEVICE_MODELS from .parser import EncryptionScheme, XiaomiBluetoothDeviceData __version__ = "0.36.0" __all__ = [ "SLEEPY_DEVICE_MODELS", "EncryptionScheme", "XiaomiBluetoothDeviceData", "SensorDescription", "SensorDeviceInfo", "DeviceClass", "DeviceKey", "SensorUpdate", "SensorDeviceInfo", "SensorValue", "Units", "XiaomiCloudBLEDevice", "XiaomiCloudException", "XiaomiCloudInvalidAuthenticationException", "XiaomiCloudInvalidPasswordException", "XiaomiCloudInvalidUsernameException", "XiaomiCloudTokenFetch", "XiaomiCloudTwoFactorAuthenticationException", ] Bluetooth-Devices-xiaomi-ble-b47183c/pyproject.toml0000664000175000017500000000514514772435047024510 0ustar billchenchinabillchenchina[tool.poetry] name = "xiaomi-ble" version = "0.36.0" description = "Manage Xiaomi BLE devices" authors = ["John Carr "] license = "Apache-2.0" readme = "README.md" repository = "https://github.com/bluetooth-devices/xiaomi-ble" documentation = "https://xiaomi-ble.readthedocs.io" classifiers = [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries", ] packages = [ { include = "xiaomi_ble", from = "src" }, ] [tool.poetry.urls] "Bug Tracker" = "https://github.com/bluetooth-devices/xiaomi-ble/issues" "Changelog" = "https://github.com/bluetooth-devices/xiaomi-ble/blob/main/CHANGELOG.md" [tool.poetry.dependencies] python = "^3.9" # Documentation Dependencies Sphinx = {version = ">=5,<8", optional = true} sphinx-rtd-theme = {version = ">=1,<4", optional = true} myst-parser = {version = ">=0.18,<3.1", optional = true} home-assistant-bluetooth = ">=1.9.2" sensor-state-data = ">=2.17.1" bluetooth-sensor-state-data = ">=1.6.0" bleak-retry-connector = ">=2.13.0" bluetooth-data-tools = ">=0.3.1" bleak = ">=0.19.5" cryptography = ">=40.0.0" pycryptodomex = ">=3.19.1" aiohttp = ">=3.10.0" orjson = ">=3.9.0" [tool.poetry.extras] docs = [ "myst-parser", "sphinx", "sphinx-rtd-theme", ] [tool.poetry.dev-dependencies] pytest = "^8.3" pytest-cov = "^6.0" black = "^25.1.0" isort = "^6.0.1" flake8 = "^7.2.0" mypy = "^1.15.0" pyupgrade = "^3.15.0" [tool.semantic_release] branch = "main" version_toml = ["pyproject.toml:tool.poetry.version"] version_variables = ["src/xiaomi_ble/__init__.py:__version__"] build_command = "pip install poetry && poetry build" [tool.pytest.ini_options] addopts = "-v -Wdefault --cov=xiaomi_ble --cov-report=term-missing:skip-covered" pythonpath = ["src"] [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", "if TYPE_CHECKING", "raise NotImplementedError", ] [tool.isort] profile = "black" known_first_party = ["xiaomi_ble", "tests"] [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true exclude = [ 'docs/.*', 'setup.py', ] [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true [[tool.mypy.overrides]] module = "docs.*" ignore_errors = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" Bluetooth-Devices-xiaomi-ble-b47183c/.gitpod.yml0000664000175000017500000000030614772435047023655 0ustar billchenchinabillchenchinatasks: - command: | pip install poetry PIP_USER=false poetry install - command: | pip install pre-commit pre-commit install PIP_USER=false pre-commit install-hooks Bluetooth-Devices-xiaomi-ble-b47183c/setup.py0000664000175000017500000000036014772435047023300 0ustar billchenchinabillchenchina#!/usr/bin/env python # This is a shim to allow GitHub to detect the package, build is done with poetry # Taken from https://github.com/Textualize/rich import setuptools if __name__ == "__main__": setuptools.setup(name="xiaomi-ble") Bluetooth-Devices-xiaomi-ble-b47183c/docs/0000775000175000017500000000000014772435047022517 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/docs/Makefile0000664000175000017500000000117514772435047024163 0ustar billchenchinabillchenchina# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) Bluetooth-Devices-xiaomi-ble-b47183c/docs/make.bat0000664000175000017500000000137414772435047024131 0ustar billchenchinabillchenchina@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/0000775000175000017500000000000014772435047024017 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/docs/source/index.md0000664000175000017500000000035214772435047025450 0ustar billchenchinabillchenchina# Welcome to Xiaomi BLE documentation! ```{toctree} :caption: Installation & Usage :maxdepth: 2 installation usage ``` ```{toctree} :caption: Project Info :maxdepth: 2 changelog contributing ``` ```{include} ../../README.md ``` Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/_static/0000775000175000017500000000000014772435047025445 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/docs/source/_static/.gitkeep0000664000175000017500000000000014772435047027064 0ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/docs/source/changelog.md0000664000175000017500000000004514772435047026267 0ustar billchenchinabillchenchina```{include} ../../CHANGELOG.md ``` Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/contributing.md0000664000175000017500000000005014772435047027043 0ustar billchenchinabillchenchina```{include} ../../CONTRIBUTING.md ``` Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/conf.py0000664000175000017500000000365214772435047025324 0ustar billchenchinabillchenchina# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) from typing import Any, List # -- Project information ----------------------------------------------------- project = "Xiaomi BLE" copyright = "2020, J. Nick Koston" author = "J. Nick Koston" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_parser", ] # The suffix of source filenames. source_suffix = [".rst", ".md"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns: List[Any] = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/installation.md0000664000175000017500000000026514772435047027045 0ustar billchenchinabillchenchina# Installation The package is published on [PyPI](https://pypi.org/project/deezer-python/) and can be installed with `pip` (or any equivalent): ```bash pip install xiaomi-ble ``` Bluetooth-Devices-xiaomi-ble-b47183c/docs/source/usage.md0000664000175000017500000000014014772435047025440 0ustar billchenchinabillchenchina# Usage To use this package, import it: ```python import xiaomi_ble ``` TODO: Document usage Bluetooth-Devices-xiaomi-ble-b47183c/poetry.lock0000664000175000017500000063610714772435047024000 0ustar billchenchinabillchenchina# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiofiles" version = "24.1.0" description = "File support for asyncio." optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_system == \"Linux\"" files = [ {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, ] [[package]] name = "aiohappyeyeballs" version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, ] [[package]] name = "aiohttp" version = "3.11.14" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"}, {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"}, {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"}, {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"}, {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"}, {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"}, {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"}, {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"}, {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"}, {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"}, {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"}, {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"}, {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"}, {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"}, {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"}, {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"}, {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"}, {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"}, {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"}, {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"}, {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"}, {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"}, {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"}, {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"}, {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"}, {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"}, {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"}, {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"}, {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"}, {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"}, {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"}, {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"}, {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"}, {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"}, {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"}, {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"}, {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"}, {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"}, {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"}, {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"}, {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"}, {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"}, {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"}, {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"}, {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"}, {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"}, {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"}, {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"}, {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"}, {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"}, {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"}, {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"}, {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"}, {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"}, {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"}, {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"}, ] [package.dependencies] aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] name = "attrs" version = "24.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "babel" version = "2.16.0" description = "Internationalization utilities" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" version = "25.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [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_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "bleak" version = "0.20.2" description = "Bluetooth Low Energy platform Agnostic Klient" optional = false python-versions = ">=3.7,<4.0" groups = ["main"] files = [ {file = "bleak-0.20.2-py3-none-any.whl", hash = "sha256:ce3106b7258212d92bb77be06f9301774f51f5bbc9f7cd50976ad794e9514dba"}, {file = "bleak-0.20.2.tar.gz", hash = "sha256:6c92a47abe34e6dea8ffc5cea9457cbff6e1be966854839dbc25cddb36b79ee4"}, ] [package.dependencies] async-timeout = {version = ">=3.0.0,<5", markers = "python_version < \"3.11\""} bleak-winrt = {version = ">=1.2.0,<2.0.0", markers = "platform_system == \"Windows\""} dbus-fast = {version = ">=1.83.0,<2.0.0", markers = "platform_system == \"Linux\""} pyobjc-core = {version = ">=9.0.1,<10.0.0", markers = "platform_system == \"Darwin\""} pyobjc-framework-CoreBluetooth = {version = ">=9.0.1,<10.0.0", markers = "platform_system == \"Darwin\""} pyobjc-framework-libdispatch = {version = ">=9.0.1,<10.0.0", markers = "platform_system == \"Darwin\""} [[package]] name = "bleak-retry-connector" version = "2.13.1" description = "A connector for Bleak Clients that handles transient connection failures" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] files = [ {file = "bleak_retry_connector-2.13.1-py3-none-any.whl", hash = "sha256:9fdab97d7f1cc1b1948412af2cc6f7721e843fb9d2f9b02b7cc26eb52c7ee486"}, {file = "bleak_retry_connector-2.13.1.tar.gz", hash = "sha256:af344bd81d0f7d33a0994e30fe9e28dfdc3cb970095cc1ba547a3b6ae2ee4543"}, ] [package.dependencies] async-timeout = ">=4.0.1" bleak = ">=0.19.0" bluetooth-adapters = {version = ">=0.15.2", markers = "platform_system == \"Linux\""} dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""} [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "bleak-winrt" version = "1.2.0" description = "Python WinRT bindings for Bleak" optional = false python-versions = "*" groups = ["main"] markers = "platform_system == \"Windows\"" files = [ {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, ] [[package]] name = "bluetooth-adapters" version = "0.16.2" description = "Tools to enumerate and find Bluetooth Adapters" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] markers = "platform_system == \"Linux\"" files = [ {file = "bluetooth_adapters-0.16.2-py3-none-any.whl", hash = "sha256:73ddb5537b2e854c3ffe6c2d07f255849d66d51a508e35b5e9a9d70c4f048a00"}, {file = "bluetooth_adapters-0.16.2.tar.gz", hash = "sha256:8919d88bdfad30b13fecf9bd67eb35eca2e096e142bbb6815888524580f33dd7"}, ] [package.dependencies] aiohttp = ">=3.8.1" async-timeout = {version = ">=3.0.0", markers = "python_version < \"3.11\""} bleak = ">=0.15.1" dbus-fast = ">=1.21.0" mac-vendor-lookup = ">=0.1.12" usb-devices = ">=0.4.5" [package.extras] docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<2.1)", "sphinx-rtd-theme (>=1,<3)"] [[package]] name = "bluetooth-data-tools" version = "0.3.1" description = "Tools for converting bluetooth data and packets" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] files = [ {file = "bluetooth_data_tools-0.3.1-py3-none-any.whl", hash = "sha256:b7ebacecec2d30d69dd83077f7dce5da2eb713f040ddb7d5573a2c60078c313b"}, {file = "bluetooth_data_tools-0.3.1.tar.gz", hash = "sha256:247e896ec963bf2204c0e77bc5ec09dafaa4e065afffe38553b9d4d526b71617"}, ] [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "bluetooth-sensor-state-data" version = "1.7.5" description = "Models for storing and converting Bluetooth Sensor State Data" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "bluetooth_sensor_state_data-1.7.5-py3-none-any.whl", hash = "sha256:a4ce3cce9839422299209e5427a6ce24061ff844fbdb61af2a485e480b8d7e23"}, {file = "bluetooth_sensor_state_data-1.7.5.tar.gz", hash = "sha256:be9319a3d70745e11689e91c013bf71d0d5570d303a49d21668777ea7854227a"}, ] [package.dependencies] home-assistant-bluetooth = ">=1.3.0" sensor-state-data = ">=2.0" [package.extras] docs = ["Sphinx (>=5,<7)", "myst-parser (>=0.18,<3.1)", "sphinx-rtd-theme (>=1,<4)"] [[package]] name = "certifi" version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = true python-versions = ">=3.6" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = true python-versions = ">=3.7" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] markers = {main = "extra == \"docs\" and sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "coverage" version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "dbus-fast" version = "1.95.2" description = "A faster version of dbus-next" optional = false python-versions = ">=3.7,<4.0" groups = ["main"] markers = "platform_system == \"Linux\"" files = [ {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:69f19fb94ac714b917c51fcc329b51695f085f779841edd6e429170f1f073f47"}, {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71e84a7ce3b050745dfb2bcd32ade891ecf6f4b06d3baa4dfe30ad09720a0be"}, {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:3ffcd1c999599da7806d7887d5ce1e9f0d814aa35af92daecab542c9b70794c1"}, {file = "dbus_fast-1.95.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:85739b90e6f983494c3c7187d4daaa50f4f4369aa1e87e876066de97ca1bcd58"}, {file = "dbus_fast-1.95.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e2c107caefa186919169e07a852edea3d931dfb6112584fa2c6e5653d91c0d2d"}, {file = "dbus_fast-1.95.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:99902ca6c10368492fdf5a28321d86c7e92e50fd742f0163599602c442232d29"}, {file = "dbus_fast-1.95.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3241252a744b0960e7eb9dcad2892af2bc695b4f47636bab2f17f6012d914ce"}, {file = "dbus_fast-1.95.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f52ef5edef41a0ed29645d7c6e1ee89b5e8f5e5c1ba9901699dcec9cfaf8d961"}, {file = "dbus_fast-1.95.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9706e10a6c24d4c03d28b1631e3882009964d5207a86cae2b42466635d7546be"}, {file = "dbus_fast-1.95.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:3277944b418063ad051e8e49144151962691188b972f1fbca7af39fdef4f8a47"}, {file = "dbus_fast-1.95.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab158bcf86c4b2e03c2f3453738a61de83c82cbed11f23331ccc9a9aba6d5b1"}, {file = "dbus_fast-1.95.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:17d743d13dbde0691adb98f1a6c87f0cf5617a4c9169b4820972dc8869095c6b"}, {file = "dbus_fast-1.95.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aefa695088405d313c703790ac503ad0b2ac1e4807393f1f02e410b6984aedd0"}, {file = "dbus_fast-1.95.2-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:ffd3665a6fbe318aadac998ae117b19214e0782397c86ab47792a120979783a6"}, {file = "dbus_fast-1.95.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43093d8e42342841c7ac69e236370c31949c35b659032888dae8b3af1fa35e9e"}, {file = "dbus_fast-1.95.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f4b26849254fafb11656fc8c10f8bce67deab683694e0fc7b56edd891d4e118"}, {file = "dbus_fast-1.95.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:331c4a26e010fd8ad3168b9680f39a7d0765507d3aadcbe5f250da0474877f14"}, {file = "dbus_fast-1.95.2-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:7aee30619c79334980a25a1e94299a751b3f276644ec0a69c76bfe57be184e7a"}, {file = "dbus_fast-1.95.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c5d344e3fe3c4b16593fad70448c66ca3130c8c692c74a921c15f18650581a7"}, {file = "dbus_fast-1.95.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9180d4d39d8688b9d8d43f427f0f2883354fa9df18a1f46d14f1762b82249d1f"}, {file = "dbus_fast-1.95.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e19173e65967581186666a47cd0af06111c7e957fb529198e532b35a47171460"}, {file = "dbus_fast-1.95.2-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:fae11a8ed320013decd2bf6a982f010fff011bf935c4f32141e804ba7963719b"}, {file = "dbus_fast-1.95.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f653085d9f96ed2e48434ba951cf4bf0229cc273a87b9799e7cf3a9a6612a56"}, {file = "dbus_fast-1.95.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05d53e0db08de6b3ba43eb110e01b9e6264e7143849ea9772af45b15575c21b2"}, {file = "dbus_fast-1.95.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e73b7dcbac5f418bd4b334ad112e40e8b3bc05c53a3fc451926450ce3bb805e"}, {file = "dbus_fast-1.95.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:76fb9d11bd6ebe1f832350ec57ada352f096f24b5731e38bcbad86ab9b27190b"}, {file = "dbus_fast-1.95.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26045c50bbd8c1674814759332333799b073216b2b2cac9e1cfebf757c7f926d"}, {file = "dbus_fast-1.95.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:bd68b5e7d7c6cd10d804d072062100a4893ad0c19bdac03b53b1809c51a4e3c7"}, {file = "dbus_fast-1.95.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2692efe0d6d6d58fb427cd0ed53ab44ced0c2ada642b0c15fd139c60a706d6"}, {file = "dbus_fast-1.95.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33cee333a15241e516ee84aaabdd952dbb1a63f9e986028574451475e97a113f"}, {file = "dbus_fast-1.95.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfd99cf624f769cf838e62d75ceb737b2866b1783a4f11a928cf38e4c906a5f"}, {file = "dbus_fast-1.95.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b842e94140e39a196d4d83e02eddcbc461b906871ba02f1c5374055f2d628c47"}, {file = "dbus_fast-1.95.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08ca9968dd46b854e5c7b00be32cb273edaf17ddc9647811ff63ae3b9dca822d"}, {file = "dbus_fast-1.95.2.tar.gz", hash = "sha256:3dd64c5cd362ceead6cc02603b6b4cbda58b2cbb6ec816a2f21b1901dfc3cb61"}, ] [[package]] name = "docutils" version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "flake8" version = "7.2.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.13.0,<2.14.0" pyflakes = ">=3.3.0,<3.4.0" [[package]] name = "frozenlist" version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] [[package]] name = "home-assistant-bluetooth" version = "1.10.4" description = "Home Assistant Bluetooth Models and Helpers" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] files = [ {file = "home_assistant_bluetooth-1.10.4-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:7c3434bdec5dcfe733d3e7c56d4a24418fcd03718dc2e7707c9133d1e48145a8"}, {file = "home_assistant_bluetooth-1.10.4.tar.gz", hash = "sha256:21216b6be9d028bc232b9188ac4dce773798c6b4e47482cc3524bfc5f82515e3"}, ] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "8.5.0" description = "Read metadata from Python packages" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\" and python_version < \"3.10\"" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" groups = ["dev"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, ] [package.extras] colors = ["colorama"] plugins = ["setuptools"] [[package]] name = "jinja2" version = "3.1.6" description = "A very fast and expressive template engine." optional = true python-versions = ">=3.7" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "mac-vendor-lookup" version = "0.1.12" description = "Find the vendor for a given MAC address" optional = false python-versions = "<4, >=3.5" groups = ["main"] markers = "platform_system == \"Linux\"" files = [ {file = "mac_vendor_lookup-0.1.12-py3-none-any.whl", hash = "sha256:aeec6eac01b07e6558d889b51f475a1e1e938e09cab409a069ab6a43b13cba58"}, {file = "mac_vendor_lookup-0.1.12.tar.gz", hash = "sha256:74e1723e177d4deb02977148d3fa04a7916f4bf93268e2afe3240529272bf80d"}, ] [package.dependencies] aiofiles = "*" aiohttp = "*" [package.extras] test = ["coverage", "pytest"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mdit-py-plugins" version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, ] [package.dependencies] markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = true python-versions = ">=3.7" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "multidict" version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" version = "1.15.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "myst-parser" version = "3.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, ] [package.dependencies] docutils = ">=0.18,<0.22" jinja2 = "*" markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "orjson" version = "3.10.16" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c"}, {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15"}, {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da"}, {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e"}, {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4"}, {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551"}, {file = "orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd"}, {file = "orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055"}, {file = "orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c"}, {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca"}, {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50"}, {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1"}, {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d"}, {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164"}, {file = "orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619"}, {file = "orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60"}, {file = "orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca"}, {file = "orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4"}, {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b"}, {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42"}, {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87"}, {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88"}, {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e"}, {file = "orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c"}, {file = "orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6"}, {file = "orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd"}, {file = "orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb"}, {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0"}, {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652"}, {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56"}, {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430"}, {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5"}, {file = "orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6"}, {file = "orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7"}, {file = "orjson-3.10.16-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c35b5c1fb5a5d6d2fea825dec5d3d16bea3c06ac744708a8e1ff41d4ba10cdf1"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9aac7ecc86218b4b3048c768f227a9452287001d7548500150bb75ee21bf55d"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e19f5102fff36f923b6dfdb3236ec710b649da975ed57c29833cb910c5a73ab"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17210490408eb62755a334a6f20ed17c39f27b4f45d89a38cd144cd458eba80b"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbbe04451db85916e52a9f720bd89bf41f803cf63b038595674691680cbebd1b"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a966eba501a3a1f309f5a6af32ed9eb8f316fa19d9947bac3e6350dc63a6f0a"}, {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e0d22f06c81e6c435723343e1eefc710e0510a35d897856766d475f2a15687"}, {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7c1e602d028ee285dbd300fb9820b342b937df64d5a3336e1618b354e95a2569"}, {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d230e5020666a6725629df81e210dc11c3eae7d52fe909a7157b3875238484f3"}, {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f8baac07d4555f57d44746a7d80fbe6b2c4fe2ed68136b4abb51cfec512a5e9"}, {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:524e48420b90fc66953e91b660b3d05faaf921277d6707e328fde1c218b31250"}, {file = "orjson-3.10.16-cp39-cp39-win32.whl", hash = "sha256:a9f614e31423d7292dbca966a53b2d775c64528c7d91424ab2747d8ab8ce5c72"}, {file = "orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905"}, {file = "orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10"}, ] [[package]] name = "packaging" version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] markers = {main = "extra == \"docs\""} [[package]] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "propcache" version = "0.2.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, ] [[package]] name = "pycodestyle" version = "2.13.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, ] [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pycryptodomex" version = "3.22.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] files = [ {file = "pycryptodomex-3.22.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:41673e5cc39a8524557a0472077635d981172182c9fe39ce0b5f5c19381ffaff"}, {file = "pycryptodomex-3.22.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:276be1ed006e8fd01bba00d9bd9b60a0151e478033e86ea1cb37447bbc057edc"}, {file = "pycryptodomex-3.22.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:813e57da5ceb4b549bab96fa548781d9a63f49f1d68fdb148eeac846238056b7"}, {file = "pycryptodomex-3.22.0-cp27-cp27m-win32.whl", hash = "sha256:d7beeacb5394765aa8dabed135389a11ee322d3ee16160d178adc7f8ee3e1f65"}, {file = "pycryptodomex-3.22.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b3746dedf74787da43e4a2f85bd78f5ec14d2469eb299ddce22518b3891f16ea"}, {file = "pycryptodomex-3.22.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5ebc09b7d8964654aaf8a4f5ac325f2b0cc038af9bea12efff0cd4a5bb19aa42"}, {file = "pycryptodomex-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:aef4590263b9f2f6283469e998574d0bd45c14fb262241c27055b82727426157"}, {file = "pycryptodomex-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5ac608a6dce9418d4f300fab7ba2f7d499a96b462f2b9b5c90d8d994cd36dcad"}, {file = "pycryptodomex-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a24f681365ec9757ccd69b85868bbd7216ba451d0f86f6ea0eed75eeb6975db"}, {file = "pycryptodomex-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:259664c4803a1fa260d5afb322972813c5fe30ea8b43e54b03b7e3a27b30856b"}, {file = "pycryptodomex-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7127d9de3c7ce20339e06bcd4f16f1a1a77f1471bcf04e3b704306dde101b719"}, {file = "pycryptodomex-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee75067b35c93cc18b38af47b7c0664998d8815174cfc66dd00ea1e244eb27e6"}, {file = "pycryptodomex-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:1a8b0c5ba061ace4bcd03496d42702c3927003db805b8ec619ea6506080b381d"}, {file = "pycryptodomex-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bfe4fe3233ef3e58028a3ad8f28473653b78c6d56e088ea04fe7550c63d4d16b"}, {file = "pycryptodomex-3.22.0-cp37-abi3-win32.whl", hash = "sha256:2cac9ed5c343bb3d0075db6e797e6112514764d08d667c74cb89b931aac9dddd"}, {file = "pycryptodomex-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:ff46212fda7ee86ec2f4a64016c994e8ad80f11ef748131753adb67e9b722ebd"}, {file = "pycryptodomex-3.22.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:5bf3ce9211d2a9877b00b8e524593e2209e370a287b3d5e61a8c45f5198487e2"}, {file = "pycryptodomex-3.22.0-pp27-pypy_73-win32.whl", hash = "sha256:684cb57812cd243217c3d1e01a720c5844b30f0b7b64bb1a49679f7e1e8a54ac"}, {file = "pycryptodomex-3.22.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c8cffb03f5dee1026e3f892f7cffd79926a538c67c34f8b07c90c0bd5c834e27"}, {file = "pycryptodomex-3.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:140b27caa68a36d0501b05eb247bd33afa5f854c1ee04140e38af63c750d4e39"}, {file = "pycryptodomex-3.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:644834b1836bb8e1d304afaf794d5ae98a1d637bd6e140c9be7dd192b5374811"}, {file = "pycryptodomex-3.22.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c506aba3318505dbeecf821ed7b9a9f86f422ed085e2d79c4fba0ae669920a"}, {file = "pycryptodomex-3.22.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7cd39f7a110c1ab97ce9ee3459b8bc615920344dc00e56d1b709628965fba3f2"}, {file = "pycryptodomex-3.22.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e4eaaf6163ff13788c1f8f615ad60cdc69efac6d3bf7b310b21e8cfe5f46c801"}, {file = "pycryptodomex-3.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eac39e237d65981554c2d4c6668192dc7051ad61ab5fc383ed0ba049e4007ca2"}, {file = "pycryptodomex-3.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab0d89d1761959b608952c7b347b0e76a32d1a5bb278afbaa10a7f3eaef9a0a"}, {file = "pycryptodomex-3.22.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e64164f816f5e43fd69f8ed98eb28f98157faf68208cd19c44ed9d8e72d33e8"}, {file = "pycryptodomex-3.22.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f005de31efad6f9acefc417296c641f13b720be7dbfec90edeaca601c0fab048"}, {file = "pycryptodomex-3.22.0.tar.gz", hash = "sha256:a1da61bacc22f93a91cbe690e3eb2022a03ab4123690ab16c46abb693a9df63d"}, ] [[package]] name = "pyflakes" version = "3.3.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pyflakes-3.3.0-py2.py3-none-any.whl", hash = "sha256:9d9d58c528cc6a9963d4e7e33fe6ff6fc622c8ad82a11fc10926f0bf425dc648"}, {file = "pyflakes-3.3.0.tar.gz", hash = "sha256:1955be314ebe8e9bdd100d5877fe10f0fd47fb2497e4f365e981a1a87cc8d9d7"}, ] [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyobjc-core" version = "9.2" description = "Python<->ObjC Interoperability Module" optional = false python-versions = ">=3.7" groups = ["main"] markers = "platform_system == \"Darwin\"" files = [ {file = "pyobjc-core-9.2.tar.gz", hash = "sha256:d734b9291fec91ff4e3ae38b9c6839debf02b79c07314476e87da8e90b2c68c3"}, {file = "pyobjc_core-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa674a39949f5cde8e5c7bbcd24496446bfc67592b028aedbec7f81dc5fc4daa"}, {file = "pyobjc_core-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc8de304ee322a1ee530b4d2daca135a49b4a49aa3cedc6b2c26c43885f4842"}, {file = "pyobjc_core-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0fa950f092673883b8bd28bc18397415cabb457bf410920762109b411789ade9"}, {file = "pyobjc_core-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:586e4cae966282eaa61b21cae66ccdcee9d69c036979def26eebdc08ddebe20f"}, {file = "pyobjc_core-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41189c2c680931c0395a55691763c481fc681f454f21bb4f1644f98c24a45954"}, {file = "pyobjc_core-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2d23ee539f2ba5e9f5653d75a13f575c7e36586fc0086792739e69e4c2617eda"}, {file = "pyobjc_core-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b9809cf96678797acb72a758f34932fe8e2602d5ab7abec15c5ac68ddb481720"}, ] [[package]] name = "pyobjc-framework-cocoa" version = "9.2" description = "Wrappers for the Cocoa frameworks on macOS" optional = false python-versions = ">=3.7" groups = ["main"] markers = "platform_system == \"Darwin\"" files = [ {file = "pyobjc-framework-Cocoa-9.2.tar.gz", hash = "sha256:efd78080872d8c8de6c2b97e0e4eac99d6203a5d1637aa135d071d464eb2db53"}, {file = "pyobjc_framework_Cocoa-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e02d8a7cc4eb7685377c50ba4f17345701acf4c05b1e7480d421bff9e2f62a4"}, {file = "pyobjc_framework_Cocoa-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b1e6287b3149e4c6679cdbccd8e9ef6557a4e492a892e80a77df143f40026d2"}, {file = "pyobjc_framework_Cocoa-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:312977ce2e3989073c6b324c69ba24283de206fe7acd6dbbbaf3e29238a22537"}, {file = "pyobjc_framework_Cocoa-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aae7841cf40c26dd915f4dd828f91c6616e6b7998630b72e704750c09e00f334"}, {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:739a421e14382a46cbeb9a883f192dceff368ad28ec34d895c48c0ad34cf2c1d"}, {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:32d9ac1033fac1b821ddee8c68f972a7074ad8c50bec0bea9a719034c1c2fb94"}, {file = "pyobjc_framework_Cocoa-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b236bb965e41aeb2e215d4e98a5a230d4b63252c6d26e00924ea2e69540a59d6"}, ] [package.dependencies] pyobjc-core = ">=9.2" [[package]] name = "pyobjc-framework-corebluetooth" version = "9.2" description = "Wrappers for the framework CoreBluetooth on macOS" optional = false python-versions = ">=3.7" groups = ["main"] markers = "platform_system == \"Darwin\"" files = [ {file = "pyobjc-framework-CoreBluetooth-9.2.tar.gz", hash = "sha256:cb2481b1dfe211ae9ce55f36537dc8155dbf0dc8ff26e0bc2e13f7afb0a291d1"}, {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:53d888742119d0f0c725d0b0c2389f68e8f21f0cba6d6aec288c53260a0196b6"}, {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:179532882126526e38fe716a50fb0ee8f440e0b838d290252c515e622b5d0e49"}, {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:256a5031ea9d8a7406541fa1b0dfac549b1de93deae8284605f9355b13fb58be"}, ] [package.dependencies] pyobjc-core = ">=9.2" pyobjc-framework-Cocoa = ">=9.2" [[package]] name = "pyobjc-framework-libdispatch" version = "9.2" description = "Wrappers for libdispatch on macOS" optional = false python-versions = ">=3.7" groups = ["main"] markers = "platform_system == \"Darwin\"" files = [ {file = "pyobjc-framework-libdispatch-9.2.tar.gz", hash = "sha256:542e7f7c2b041939db5ed6f3119c1d67d73ec14a996278b92485f8513039c168"}, {file = "pyobjc_framework_libdispatch-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88d4091d4bcb5702783d6e86b4107db973425a17d1de491543f56bd348909b60"}, {file = "pyobjc_framework_libdispatch-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a67b007113328538b57893cc7829a722270764cdbeae6d5e1460a1d911314df"}, {file = "pyobjc_framework_libdispatch-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6fccea1a57436cf1ac50d9ebc6e3e725bcf77f829ba6b118e62e6ed7866d359d"}, {file = "pyobjc_framework_libdispatch-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eba747b7ad91b0463265a7aee59235bb051fb97687f35ca2233690369b5e4e4"}, {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e835495860d04f63c2d2f73ae3dd79da4222864c107096dc0f99e8382700026"}, {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1b107e5c3580b09553030961ea6b17abad4a5132101eab1af3ad2cb36d0f08bb"}, {file = "pyobjc_framework_libdispatch-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:83cdb672acf722717b5ecf004768f215f02ac02d7f7f2a9703da6e921ab02222"}, ] [package.dependencies] pyobjc-core = ">=9.2" [[package]] name = "pytest" version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pyupgrade" version = "3.19.1" description = "A tool to automatically upgrade syntax for newer versions." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pyupgrade-3.19.1-py2.py3-none-any.whl", hash = "sha256:8c5b0bfacae5ff30fa136a53eb7f22c34ba007450d4099e9da8089dabb9e67c9"}, {file = "pyupgrade-3.19.1.tar.gz", hash = "sha256:d10e8c5f54b8327211828769e98d95d95e4715de632a3414f1eef3f51357b9e2"}, ] [package.dependencies] tokenize-rt = ">=6.1.0" [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sensor-state-data" version = "2.18.1" description = "Models for storing and converting Sensor Data state" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ {file = "sensor_state_data-2.18.1-py3-none-any.whl", hash = "sha256:45a223acf5d4aefde45c028fa810c7925db6448984097aa1b500fe4f206d113f"}, {file = "sensor_state_data-2.18.1.tar.gz", hash = "sha256:25f17ed98748ae006ddab82d5013cf30301daaf23526d1992f99c4dc0beb49c3"}, ] [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = true python-versions = "*" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sphinx" version = "7.4.7" description = "Python documentation generator" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] alabaster = ">=0.7.14,<0.8.0" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} docutils = ">=0.20,<0.22" imagesize = ">=1.3" importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-rtd-theme" version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, ] [package.dependencies] docutils = ">0.18,<0.22" sphinx = ">=6,<9" sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "transifex-client", "twine", "wheel"] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = true python-versions = ">=2.7" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, ] [package.dependencies] Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = true python-versions = ">=3.5" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "tokenize-rt" version = "6.1.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "tokenize_rt-6.1.0-py2.py3-none-any.whl", hash = "sha256:d706141cdec4aa5f358945abe36b911b8cbdc844545da99e811250c0cee9b6fc"}, {file = "tokenize_rt-6.1.0.tar.gz", hash = "sha256:e8ee836616c0877ab7c7b54776d2fefcc3bde714449a206762425ae114b53c86"}, ] [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] markers = {main = "extra == \"docs\" and python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] markers = {main = "python_version < \"3.11\""} [[package]] name = "urllib3" version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\"" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "usb-devices" version = "0.4.5" description = "Tools for mapping, describing, and resetting USB devices" optional = false python-versions = ">=3.9,<4.0" groups = ["main"] markers = "platform_system == \"Linux\"" files = [ {file = "usb_devices-0.4.5-py3-none-any.whl", hash = "sha256:8a415219ef1395e25aa0bddcad484c88edf9673acdeae8a07223ca7222a01dcf"}, {file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"}, ] [[package]] name = "yarl" version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.0" [[package]] name = "zipp" version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"docs\" and python_version < \"3.10\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] docs = ["Sphinx", "myst-parser", "sphinx-rtd-theme"] [metadata] lock-version = "2.1" python-versions = "^3.9" content-hash = "d9968446c731e1ccb4ef1be1dcda856b6177d67068494facf205781ad0b62d5f" Bluetooth-Devices-xiaomi-ble-b47183c/.pre-commit-config.yaml0000664000175000017500000000305714772435047026055 0ustar billchenchinabillchenchina# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "CHANGELOG.md" default_stages: [pre-commit] ci: autofix_commit_msg: "chore(pre-commit.ci): auto fixes" autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" repos: - repo: https://github.com/commitizen-tools/commitizen rev: v4.4.1 hooks: - id: commitizen stages: [commit-msg] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: debug-statements - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-toml - id: check-xml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace - id: debug-statements # - repo: https://github.com/pre-commit/mirrors-prettier # rev: v3.1.0 # hooks: # - id: prettier - repo: https://github.com/asottile/pyupgrade rev: v3.19.1 hooks: - id: pyupgrade - repo: https://github.com/PyCQA/isort rev: 6.0.1 hooks: - id: isort - repo: https://github.com/psf/black-pre-commit-mirror rev: 25.1.0 hooks: - id: black - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell - repo: https://github.com/PyCQA/flake8 rev: 7.1.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.15.0 hooks: - id: mypy additional_dependencies: [] Bluetooth-Devices-xiaomi-ble-b47183c/renovate.json0000664000175000017500000000010114772435047024275 0ustar billchenchinabillchenchina{ "extends": ["github>browniebroke/renovate-configs:python"] } Bluetooth-Devices-xiaomi-ble-b47183c/commitlint.config.mjs0000664000175000017500000000036214772435047025726 0ustar billchenchinabillchenchinaexport default { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [0, "always", Infinity], "body-max-line-length": [0, "always", Infinity], "footer-max-line-length": [0, "always", Infinity], }, }; Bluetooth-Devices-xiaomi-ble-b47183c/.readthedocs.yml0000664000175000017500000000100414772435047024650 0ustar billchenchinabillchenchina# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.9" # Optionally declare the Python requirements required to build your docs python: install: - method: pip path: . extra_requirements: - docs Bluetooth-Devices-xiaomi-ble-b47183c/CHANGELOG.md0000664000175000017500000015435214772435047023412 0ustar billchenchinabillchenchina# CHANGELOG ## v0.36.0 (2025-03-31) ### Features - Add ks1 and ks1bp ([#156](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/156), [`415a3b3`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/415a3b38b113256e700284198d9a2d911886d79a)) ## v0.35.0 (2025-03-29) ### Chores - Update dependabot.yml to include GHA ([`67fe52b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/67fe52b8ded47f0c2bfbfe5fe14bb4ecfae3dfac)) - **deps**: Bump orjson from 3.10.15 to 3.10.16 ([#154](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/154), [`b90653c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b90653c96b35c2fa573c6207ec0f868f1f65fb2b)) Bumps [orjson](https://github.com/ijl/orjson) from 3.10.15 to 3.10.16. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.10.15...3.10.16) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-ci**: Bump the github-actions group with 8 updates ([#152](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/152), [`e1d1f5c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/e1d1f5c4f00c33ca7f680cf132b9ff0a16ba4f03)) * chore(deps-ci): bump the github-actions group with 8 updates Bumps the github-actions group with 8 updates: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `3` | `4` | | [actions/setup-python](https://github.com/actions/setup-python) | `3` | `5` | | [pre-commit/action](https://github.com/pre-commit/action) | `3.0.0` | `3.0.1` | | [wagoid/commitlint-github-action](https://github.com/wagoid/commitlint-github-action) | `5` | `6` | | [codecov/codecov-action](https://github.com/codecov/codecov-action) | `3` | `5` | | [relekang/python-semantic-release](https://github.com/relekang/python-semantic-release) | `7.34.6` | `9.21.0` | | [browniebroke/hacktoberfest-labeler-action](https://github.com/browniebroke/hacktoberfest-labeler-action) | `2.2.0` | `2.3.0` | | [tiangolo/issue-manager](https://github.com/tiangolo/issue-manager) | `0.4.0` | `0.5.1` | Updates `actions/checkout` from 3 to 4 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) Updates `actions/setup-python` from 3 to 5 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3...v5) Updates `pre-commit/action` from 3.0.0 to 3.0.1 - [Release notes](https://github.com/pre-commit/action/releases) - [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1) Updates `wagoid/commitlint-github-action` from 5 to 6 - [Changelog](https://github.com/wagoid/commitlint-github-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/wagoid/commitlint-github-action/compare/v5...v6) Updates `codecov/codecov-action` from 3 to 5 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v5) Updates `relekang/python-semantic-release` from 7.34.6 to 9.21.0 - [Release notes](https://github.com/relekang/python-semantic-release/releases) - [Changelog](https://github.com/python-semantic-release/python-semantic-release/blob/master/CHANGELOG.rst) - [Commits](https://github.com/relekang/python-semantic-release/compare/v7.34.6...v9.21.0) Updates `browniebroke/hacktoberfest-labeler-action` from 2.2.0 to 2.3.0 - [Release notes](https://github.com/browniebroke/hacktoberfest-labeler-action/releases) - [Changelog](https://github.com/browniebroke/hacktoberfest-labeler-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/browniebroke/hacktoberfest-labeler-action/compare/v2.2.0...v2.3.0) Updates `tiangolo/issue-manager` from 0.4.0 to 0.5.1 - [Release notes](https://github.com/tiangolo/issue-manager/releases) - [Commits](https://github.com/tiangolo/issue-manager/compare/0.4.0...0.5.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-python dependency-type: direct:production - dependency-name: pre-commit/action dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: wagoid/commitlint-github-action dependency-type: direct:production - dependency-name: codecov/codecov-action dependency-type: direct:production - dependency-name: relekang/python-semantic-release dependency-type: direct:production - dependency-name: browniebroke/hacktoberfest-labeler-action dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: tiangolo/issue-manager dependency-type: direct:production dependency-group: github-actions ... Signed-off-by: dependabot[bot] * chore: update commitlint config * chore: update pyproject for new psr * chore: migrate workflow to upload python package --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston - **deps-dev**: Bump flake8 from 7.1.2 to 7.2.0 ([#153](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/153), [`7d91379`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7d91379e515a51f49de12f54c05abcc65584d2d4)) Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.2 to 7.2.0. - [Commits](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ### Features - Use trusted publishing for PyPI ([#155](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/155), [`95a54ad`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/95a54ad52cff260b26f3b05cd865f028afc3905d)) ## v0.34.1 (2025-03-29) ### Bug Fixes - Rewrite struct ([#151](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/151), [`98e313b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/98e313be6b057407385893833f4d6d9482568515)) ## v0.34.0 (2025-03-29) ### Chores - Add Python 3.13 to the CI ([#115](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/115), [`534a5e8`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/534a5e81448f6f52f76c754ce38a50582eb1c168)) - Create dependabot.yml ([`b291db2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b291db278bf3fedcf0f9b678a0eb1c95f5898c45)) - Update deps for Python 3.13 ([#123](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/123), [`ee474e7`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/ee474e789df55f69f0eb77dfd82e6610ddf35461)) - **deps**: Bump aiohttp from 3.10.10 to 3.11.11 ([#110](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/110), [`a32edf3`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/a32edf330de1a0906f3df0f45b9672847e834b85)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump aiohttp from 3.11.11 to 3.11.12 ([#137](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/137), [`ae27c17`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/ae27c1732682acd08d3a1f778e5b02a9a5960cab)) - **deps**: Bump aiohttp from 3.11.12 to 3.11.13 ([#142](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/142), [`a20032e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/a20032e70f083bda4fb9d24536df4f402dfb2f38)) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.11.12 to 3.11.13. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.11.12...v3.11.13) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump aiohttp from 3.11.13 to 3.11.14 ([#144](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/144), [`237e9fe`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/237e9fe04a73ee6678b41ccdf4424d52143755d1)) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump bluetooth-sensor-state-data from 1.7.1 to 1.7.5 ([#136](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/136), [`8df0798`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8df07985636c5650c8802ff837f5959704005ac8)) - **deps**: Bump certifi from 2023.11.17 to 2024.7.4 ([#116](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/116), [`32ece70`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/32ece7046f5a44b1476a7d00919d3a7a6b4f8bcb)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump cryptography from 41.0.7 to 43.0.1 ([#120](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/120), [`b5c6524`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b5c6524760731d953b2bc01fe4d1d626758656fa)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump idna from 3.6 to 3.7 ([#118](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/118), [`ff0ccac`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/ff0ccac7c2921f09c2aaec7d6bc7aa8fdd4919b2)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump jinja2 from 3.1.2 to 3.1.5 ([#117](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/117), [`76120db`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/76120db87270e1ddcc3fbb3f980f1b6ebee5f299)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump jinja2 from 3.1.5 to 3.1.6 ([#146](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/146), [`8f64c7b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8f64c7b7b92c6e8d64d2f874f9bb03793ff46bd7)) Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6) --- updated-dependencies: - dependency-name: jinja2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump myst-parser from 0.18.1 to 1.0.0 ([#126](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/126), [`f9f1e0b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f9f1e0bfb9a764c56cd4527d74f49f6bd66d05c6)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump myst-parser from 1.0.0 to 3.0.1 ([#132](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/132), [`68e4f90`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/68e4f90283e1806e65600234d4730f49e166cb5c)) Bumps [myst-parser](https://github.com/executablebooks/MyST-Parser) from 1.0.0 to 3.0.1. - [Release notes](https://github.com/executablebooks/MyST-Parser/releases) - [Changelog](https://github.com/executablebooks/MyST-Parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/executablebooks/MyST-Parser/compare/v1.0.0...v3.0.1) --- updated-dependencies: - dependency-name: myst-parser dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump pycryptodomex from 3.21.0 to 3.22.0 ([#145](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/145), [`492010a`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/492010a3606a1e6426dd7d4e3a22360214ac75ca)) - **deps**: Bump requests from 2.31.0 to 2.32.2 ([#121](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/121), [`b1355f2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b1355f262cb134aecb794552d82206bab0bf7c9b)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump sphinx from 5.3.0 to 6.2.1 ([#127](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/127), [`a2da09e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/a2da09e57f8add15f4b2939d3df9b4bf88821f61)) - **deps**: Bump sphinx from 6.2.1 to 7.4.7 ([#134](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/134), [`6a5e11c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/6a5e11cd5a0140542c2a2f02cfaaeb60e2b60a43)) - **deps**: Bump sphinx-rtd-theme from 1.3.0 to 2.0.0 ([#125](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/125), [`f3133ed`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f3133ed85471250f4e5f9ffc9bd72d6f591fe058)) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.3.0 to 2.0.0. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.3.0...2.0.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps**: Bump sphinx-rtd-theme from 2.0.0 to 3.0.2 ([#131](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/131), [`bba51cd`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bba51cd0a262e520ae9f8a3b15c3517aa9f047f9)) - **deps**: Bump urllib3 from 2.1.0 to 2.2.2 ([#119](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/119), [`372fe44`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/372fe44d7164fbba9231353def30865679e337da)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump black from 23.12.1 to 24.10.0 ([#114](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/114), [`3f74088`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/3f74088640367cfc79a2c334793cba1a1754e44a)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump black from 24.10.0 to 25.1.0 ([#129](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/129), [`00df853`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/00df853e955ee6cfaf7397d0924ed96829b09d50)) - **deps-dev**: Bump flake8 from 7.0.0 to 7.1.1 ([#111](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/111), [`ee96510`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/ee96510187c70b698c5def2c21aa86012a141879)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump flake8 from 7.1.1 to 7.1.2 ([#139](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/139), [`40baa29`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/40baa299a47e744f0a465d7a6f21eebec8196bfb)) Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.1 to 7.1.2. - [Commits](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump isort from 5.13.2 to 6.0.0 ([#130](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/130), [`7f9cb1c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7f9cb1c0cb408ffbf832ec20ab3a430f0f93b91c)) Bumps [isort](https://github.com/pycqa/isort) from 5.13.2 to 6.0.0. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.13.2...6.0.0) --- updated-dependencies: - dependency-name: isort dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump isort from 6.0.0 to 6.0.1 ([#143](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/143), [`e57a9e6`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/e57a9e6f0e8333bf88620c0ea405088b81963486)) Bumps [isort](https://github.com/PyCQA/isort) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/PyCQA/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/PyCQA/isort/compare/6.0.0...6.0.1) --- updated-dependencies: - dependency-name: isort dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump mypy from 1.14.1 to 1.15.0 ([#135](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/135), [`1ac5955`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/1ac5955acd59087ca10e7ad169ae3aa3ef8eaf25)) Bumps [mypy](https://github.com/python/mypy) from 1.14.1 to 1.15.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.14.1...v1.15.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump mypy from 1.8.0 to 1.14.1 ([#113](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/113), [`f3cceb4`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f3cceb42c506bb2142e4f74f8f6194061631028b)) - **deps-dev**: Bump pytest from 7.4.4 to 8.3.4 ([#112](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/112), [`9a4109c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/9a4109c8ada297babd478781e80c23f8edc81907)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump pytest from 8.3.4 to 8.3.5 ([#141](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/141), [`7a1f267`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7a1f267107c5255fd7e70a60d8e3b7deb4d17a8e)) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.4...8.3.5) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump pytest-cov from 4.1.0 to 6.0.0 ([#124](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/124), [`1df7541`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/1df7541126830f03ca2de0886d25c1a2d1745b16)) Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.1.0 to 6.0.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...v6.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump zipp from 3.17.0 to 3.19.1 ([#122](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/122), [`bafacf5`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bafacf57b1aa4cddabd9c755e668943cfa65b3ba)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#101](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/101), [`7951a00`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7951a002e3570d42be328d3d0f16a93048690ef0)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#102](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/102), [`6599a90`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/6599a907cd470a606863d7bf9c456696d3298a9a)) updates: - [github.com/commitizen-tools/commitizen: v3.30.1 → v3.31.0](https://github.com/commitizen-tools/commitizen/compare/v3.30.1...v3.31.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#103](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/103), [`fb68075`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fb68075c12fd2f652d83387df5496b63006e1d8b)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#104](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/104), [`705bc17`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/705bc1722be9a74d5692f8a237e0c5bbb0e4821c)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#106](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/106), [`c4bc481`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c4bc481244624e20c1ba026891ee2482c4c4024e)) updates: - [github.com/asottile/pyupgrade: v3.19.0 → v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.19.0...v3.19.1) - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#109](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/109), [`f12043e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f12043e357a25abda66460e2b12b1aeac70e2a92)) - **pre-commit.ci**: Pre-commit autoupdate ([#128](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/128), [`28485cd`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/28485cdbac8415b1d00bed504f4865482b270960)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#133](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/133), [`becc164`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/becc164c9cfd4cc0e332b05689fef869f693181c)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#138](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/138), [`82c2fcb`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/82c2fcbc445eaf831a613d37fa1ebeb38dfdd4ac)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#140](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/140), [`6170b12`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/6170b12726629107c43da324e056084df1dd7187)) updates: - [github.com/commitizen-tools/commitizen: v4.2.1 → v4.4.1](https://github.com/commitizen-tools/commitizen/compare/v4.2.1...v4.4.1) - [github.com/PyCQA/isort: 6.0.0 → 6.0.1](https://github.com/PyCQA/isort/compare/6.0.0...6.0.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#99](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/99), [`6f9b5c2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/6f9b5c2bf069083fd795bcfa7c601ae86abd1d3a)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Add support for MJWSD06MMC ([#150](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/150), [`51cbe1c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/51cbe1c364d6983decf9596fee8d1caf4501ff84)) ## v0.33.0 (2024-10-21) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#97](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/97), [`cf7a512`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/cf7a512c931326e6a64ae1e462e4e3ebed71c3b0)) updates: - [github.com/commitizen-tools/commitizen: v3.29.0 → v3.29.1](https://github.com/commitizen-tools/commitizen/compare/v3.29.0...v3.29.1) - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/asottile/pyupgrade: v3.17.0 → v3.18.0](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.18.0) - [github.com/psf/black-pre-commit-mirror: 24.8.0 → 24.10.0](https://github.com/psf/black-pre-commit-mirror/compare/24.8.0...24.10.0) - [github.com/PyCQA/bandit: 1.7.9 → 1.7.10](https://github.com/PyCQA/bandit/compare/1.7.9...1.7.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Add support for extracting cloud keys ([#98](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/98), [`fdc3b90`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fdc3b90cc07cd7e86e031c78bbb379800f549e72)) ## v0.32.0 (2024-09-07) ### Features - Support xiaomi motion sensor 2s - global version (XMPIR02SGXS) ([#95](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/95), [`7e30569`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7e30569a56b2668cd88ac1b4ee3d7454901e0c37)) It's actually the same as the China mainland version (XMPIR02SXS vs XMPIR02SGXS). The only change is the device id as the code change indicates. The change is manually verified by Home Assistant (no change except use the new xiaomi-ble package) Hardware Info: - https://www.mi.com/global/product/xiaomi-motion-sensor-2s/ ## v0.31.1 (2024-08-28) ### Bug Fixes - Add xmosb01xs as a sleepy device ([#94](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/94), [`648c7f8`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/648c7f80e4c1e9e9868b974e49e7da564457c094)) this battery device is really a sleepy device. it won't broadcast any occupancy message unless it is changed. ## v0.31.0 (2024-08-27) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#85](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/85), [`86c8e6b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/86c8e6b0388c8afd3a6787587d5575f263eac4ab)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#86](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/86), [`c9aa15c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c9aa15c7a41af6e83c1d6bed535e1a6b59f0a92e)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#87](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/87), [`daa5786`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/daa5786e8488b2cdaf823eee5dd2236bdc6ce6fe)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#88](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/88), [`8ec6e14`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8ec6e14e0b7a27205d763fb7c5a8ca01218dc634)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#89](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/89), [`cf115f6`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/cf115f6b94ab461afab6cc2c4f6d4f64b310ccae)) updates: - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Support xiaomi occupancy(human presence) sensor (XMOSB01XS) ([#93](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/93), [`f10c72b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f10c72b166572e8e711469da693192cdab5049c2)) feat: support 2024 new battery based xiaomi mijia occupancy(human presence) sensor (XMOSB01XS) New test cases are added. Also the change is verified by Home Assistant (with a small additional change in xiaomi-ble component for additional new properties) Hardware Info: - https://www.aliexpress.com/i/1005007104780534.html - https://www.mi.com/shop/buy/detail?product_id=19994 ## v0.30.2 (2024-07-05) ### Bug Fixes - Force release ([`cf7d48c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/cf7d48c2194666eaede56ebd9ab3bfbc5ddfe3b1)) - Force release ([`7bcbda1`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7bcbda1a6468fad98a0959cc05001ddbd478b81b)) - Force release ([`bae0ff3`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bae0ff33e69ac4b40f5e6f090e3dbfd7af241ff8)) - Force release ([`2ed0584`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2ed05848718d1d80a7edb410bd7a229f8334d4de)) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#82](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/82), [`0ee3610`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/0ee3610c1df7e24f32417c8ba5fd37d4a1700ced)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ## v0.30.1 (2024-06-25) ### Bug Fixes - Fix license classifier ([#81](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/81), [`27072da`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/27072da63e01204f3bae81b7ddf734790896f0b5)) Looks good :) thanks ## v0.30.0 (2024-06-17) ### Features - Extend battery level reading to gcls002 ([`49530c5`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/49530c53893da290c26c4236d714a67df58612ff)) feat: extend battery level reading to gcls002 ## v0.29.0 (2024-05-05) ### Features - Add support for Linptech RS1BB(MI) ([#77](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/77), [`57d070c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/57d070c71282c43882bc64117c709cdf1aec8f09)) * feat: add support for RS1BB ## v0.28.0 (2024-03-24) ### Features - Allow one failed decryption before reauth ([#76](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/76), [`4682109`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/46821098f2d34b4c0c7c0244708500b4aa72f19e)) ## v0.27.1 (2024-03-17) ### Bug Fixes - Remove mac workaround ([#75](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/75), [`c5a900c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c5a900c5d6e23b62769a16c0840ceed174c3a8da)) ## v0.27.0 (2024-03-10) ### Features - Add ptx and xmpiro2sx ([#74](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/74), [`b0bb42f`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b0bb42f777c9c5bdc6bc12590334bfc741b199a0)) * feat: add ptx and xmpiro2sx * fix: rename ptx ## v0.26.2 (2024-03-10) ### Bug Fixes - Handle jagged stairstep for LYWSD03MMC humidity ([#73](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/73), [`efb3229`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/efb322909933ac820b32012913ff3c35f9ca3659)) * fix: handle jagged stairstep for LYWSD03MMC humidity ## v0.26.1 (2024-02-18) ### Bug Fixes - Typo in lock events ([#71](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/71), [`9918c70`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/9918c7004f4ed4d12045bcb486dc762064e8510f)) ## v0.26.0 (2024-02-18) ### Features - Add support for locks and fingerprint readers ([#70](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/70), [`2cffc70`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2cffc70d451614a671833274947911a123a8f3b0)) * feat: add support for locks and fingerprint readers * fix: add key id type * fix: typo in fingerprint event * fix: add fingerprint event * fix: typo in lock event ## v0.25.2 (2024-02-02) ### Bug Fixes - Proper event_properties formatting ([#69](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/69), [`86bced3`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/86bced38755551014d1273c69c6b80b79e3eb05a)) * fix: proper event_properties formatting * fix: fix tests property_events * fix: formatting of the tests ## v0.25.1 (2024-02-02) ### Bug Fixes - Rename rubiks_cube to cube ([#68](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/68), [`65c7b3d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/65c7b3d830bf9256ea60b8d8a15236a49b6ddd65)) ## v0.25.0 (2024-02-02) ### Features - Add dimmer and rubiks cube events ([#67](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/67), [`33db2b0`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/33db2b09d360a9e2f010ac2ad2ec5d199e65cf3f)) ## v0.24.1 (2024-02-01) ### Bug Fixes - Postfix plus and min ([#66](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/66), [`d8caf82`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/d8caf825ea08bda7101c13f15d50db053c667e3c)) ## v0.24.0 (2024-01-31) ### Features - Add remote buttons ([#65](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/65), [`380a803`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/380a80301e126150e87bb67e47c30c93dd28c0ff)) ## v0.23.1 (2024-01-28) ### Bug Fixes - Use postfix id for double buttons ([#64](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/64), [`6469a6d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/6469a6dc2319549d5ee915135738573436c959da)) ## v0.23.0 (2024-01-27) ### Features - Add button devices with multiple buttons ([#63](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/63), [`740679c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/740679ce362d4201c816655e997f8223021fa7a1)) ## v0.22.0 (2024-01-27) ### Features - Add events for single button devices ([#62](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/62), [`2110db9`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2110db95c2670bfeda24ed7c0d0bf87bf559d45e)) ## v0.21.2 (2024-01-11) ### Bug Fixes - Bump pycryptodomex ([#61](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/61), [`8bcefae`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8bcefaed9b8aa106cf37321793e147287779d4da)) * fix: bump pycryptodomex * fix: update poetry lock file * chore: run poetry update --------- Co-authored-by: J. Nick Koston ### Chores - Fix release ([`1d403f0`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/1d403f0b782fc1e0ba738cf9660576429286b7a5)) ## v0.21.1 (2023-07-26) ### Bug Fixes - Add unit of measurement to counter ([#56](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/56), [`22956f8`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/22956f87269859e9ca94fb96c55a9a90fe45a4ac)) fix: add unit of measurment to counter ## v0.21.0 (2023-07-26) ### Features - Add support for toothbrushes ([#55](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/55), [`b3534f9`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b3534f9daeef535372ffd5746d6662b1869f084e)) * feat: add support for toothbrushes * feat: add score sensor for toothbrushes * fix: precommit errors ## v0.20.0 (2023-07-23) ### Features - Add mi scale v1 ([#54](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/54), [`57dc01a`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/57dc01a0f665af945540f7d01356030ce065b96a)) * feat: add mi scale v1 * fix: check for mass not removed * fix: check for mass removed before reporting final data ## v0.19.1 (2023-07-20) ### Bug Fixes - Remove some dependencies ([#53](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/53), [`9d11d62`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/9d11d620557ec95d0bdf9112b8cf69e82031a006)) * fix: remove some dependencies * fix: poetry lock ## v0.19.0 (2023-07-20) ### Features - Switch from pycryptodomex to cryptography for v4 and v5 ([#48](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/48), [`382beea`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/382beeadea23216c185428d2ada784a3e6fe6660)) * feat: switch from pycryptodomex to cryptography for v4 and v5 ## v0.18.2 (2023-07-19) ### Bug Fixes - Use only kg for mass ([#51](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/51), [`93b5c9f`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/93b5c9ff0e0360b4c54d4bae603d0eb45198c8a3)) ### Chores - Re-release ([#52](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/52), [`73b626d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/73b626d954e67b57f6fdff470e9acbfb8a172370)) ## v0.18.1 (2023-07-17) ### Bug Fixes - Use sensor state data for scale sensors ([#50](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/50), [`fcfa510`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fcfa5100dfebb9dd451afdb7395eef530c7f3abb)) * fix: use sensor state data for scale sensors * chore: improve coverage ## v0.18.0 (2023-07-16) ### Chores - Bump python semantic release ([#49](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/49), [`fa35709`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fa357098f63c63478eb1fa83e2b220e67160430b)) ### Features - Add support for mi body composition scale 2 ([#47](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/47), [`3755aed`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/3755aede1da4e4a11c5534ef1b731a33f6f2b39c)) * feat: add support for mi body composition scale 2 * fix: correct scale device type * test: add test for scale parser ## v0.17.2 (2023-06-03) ### Bug Fixes - Jtyjgd03mmi is now detected as smoke sensor ([#46](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/46), [`9e06702`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/9e0670252ebe9802fdf5eaec030f23813090da02)) ## v0.17.1 (2023-05-21) ### Bug Fixes - Add sleepy_device property ([#44](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/44), [`fac1009`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fac1009acdd4a31daa9e50eb6fffea6d0bff1702)) * fix: add sleepy_device property * fix: shorten code ## v0.17.0 (2023-04-03) ### Features - Add updated lywsd02mmc device type and mmc w505 body temperature sensor ([`23f85e2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/23f85e29e887e042fe2eeea83b15ca2e1beeea08)) ## v0.16.4 (2023-02-17) ### Bug Fixes - Add jtyjgd03mi to sleepy devices ([#42](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/42), [`bcf72a2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bcf72a21f95ba932f361b203bee9dc804776c15b)) ## v0.16.3 (2023-02-09) ### Bug Fixes - Add rtcgq02lm to sleepy devices ([#41](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/41), [`899e084`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/899e0845211d1ab796a67379c0d99393828c02df)) ## v0.16.2 (2023-02-07) ### Bug Fixes - Add sleepy devices ([#40](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/40), [`70137b4`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/70137b4addd9d67617de4d64464872f2a146928d)) * fix: add sleepy devices * fix: use set for sleepy devices ## v0.16.1 (2023-02-05) ### Bug Fixes - Relax bleak-retry-connector ([#38](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/38), [`59abc8b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/59abc8be1ea3e0a6c3c94353f54e5d131c155a11)) ### Chores - Add py311 to the CI ([#39](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/39), [`642de25`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/642de25a941e1b729be643dd7e31aa83efd4431d)) ## v0.16.0 (2023-02-05) ### Bug Fixes - Bump python-semantic-release ([#37](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/37), [`f2703fd`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f2703fd49c0f4aafdba6983e0cf92bf4b2558882)) - Bump version ([`f9d9eb9`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f9d9eb9656bde85124b4340cac79d1ccd8da748e)) - Force release ([#35](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/35), [`5e9c736`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/5e9c736bfaa035a6418c59031ec6ab137fee5e06)) - Force release ([#36](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/36), [`3f94b0f`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/3f94b0fd4449cd015858a6ed32426727a0be79a1)) - Revert version bump ([`51d208a`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/51d208ac8262d0d7af96ceb10b8c5856402f8ad4)) - Revert version bump ([`bd8f028`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bd8f0284a9011bb45b94879152f66619f71fd72f)) - Update dependencies ([`e0bce32`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/e0bce3286b65c5e328c1b29ab82c7817fa51d24c)) * fix: update poetry and pyproject file * fix: bump isort * fix: bump isort in precommit ### Features - Bump version ([`880f12d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/880f12d39f18c07e291cc26d832cba65817a412f)) - Implement motion timeout ([`2818f1f`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2818f1f6b25410168d59ca3f342801d222cee11e)) feat(RTCGQ02LM): implement motion timeout ## v0.15.0 (2023-01-04) ### Features - Motion events ([#31](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/31), [`2a52c1e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2a52c1eaafa02b7f00c649cebad0a3d304e5d15d)) ## v0.14.3 (2023-01-03) ### Bug Fixes - Assign unique keys to device class ([#30](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/30), [`09a9fbb`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/09a9fbb0a088357ac585805bd1bb0db5def9c75c)) ## v0.14.2 (2023-01-03) ### Bug Fixes - Extend binary sensor enums ([#29](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/29), [`4563a9b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/4563a9bdf86bc1cd40dd86a141437bf20de1f7d6)) * fix: extend binary sensor enums * fix: typo * fix: add base device class * fix: separate enum ## v0.14.1 (2023-01-02) ### Bug Fixes - Lower bleak version requirement ([#28](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/28), [`c9a80ac`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c9a80ac1f6fcc433cd9499092c759d195309c2e4)) * fix: lower bleak dependency requirement * fix: bleak dependency ## v0.14.0 (2023-01-01) ### Features - Add new xiaomi sensors ([#27](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/27), [`a39a5f9`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/a39a5f9c22fa7e497891bbaa147a4f756c922afa)) * feat: add new xiaomi sensors ## v0.13.0 (2023-01-01) ### Features - Add door opening and motion sensors ([#26](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/26), [`3e0babd`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/3e0babdde9a69aabd470506f908be55673708671)) * feat: add more binary sensors * fix: fix tests and bump dependecies * fix: add more tests * feat: add problem sensors for opening and door * feat: add motion sensors * fix: remove mode sensor for now * fix: do not use predefined sensors * fix: remove mue4094rt ## v0.12.2 (2022-11-15) ### Bug Fixes - Bump sensor-state-data ([#24](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/24), [`c2dd39c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c2dd39ca898910c6458f90033068d8924ecadf51)) ## v0.12.1 (2022-11-13) ### Bug Fixes - Unit of measurement formaldehyde ([#23](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/23), [`48035a1`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/48035a1dd704c23d1b8e74be0c7342d7d7da61b7)) ## v0.12.0 (2022-11-12) ### Features - Add support for HHCCJCY10 ([#21](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/21), [`0f387c2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/0f387c244644a8469de935ce4e5800d882868d81)) * feat: add support for HHCCJCY10 * feat: add new device_classes Co-authored-by: Ernst Klamer ## v0.11.0 (2022-11-09) ### Features - Add linptech ms1bb and hs1bb ([`52a21dd`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/52a21dd062d9a2172c8e23008f1ee80bbef6ce5e)) * feat: add linptech m1sbb * fix: fix test m1sbb * fix: use bind key in m1sbb test * fix: improve test coverage * fix: fix button test * fix: formatting * Add HS1BB(MI) motion sensor * fix: formatting issues * fix: linting issue ## v0.10.0 (2022-09-13) ### Features - Update for bleak 0.17 support ([#19](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/19), [`db14912`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/db14912f6c57f20363e85d49fe320765606e7ed3)) ## v0.9.3 (2022-09-12) ### Bug Fixes - Bump bleak-retry-connector ([#18](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/18), [`08fc5aa`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/08fc5aab0e6cf2e772cff962c7cf801328ef66e9)) ## v0.9.2 (2022-08-25) ### Bug Fixes - Use short_address from bluetooth_data_tools ([#16](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/16), [`d370cca`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/d370cca23c4c585437de5946d33383a550d06bc3)) ## v0.9.1 (2022-08-16) ### Bug Fixes - Rename Smart Door Lock E ([#13](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/13), [`ef11f5f`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/ef11f5fd799e9a67223b8dbe5569a33c3e214bc0)) ## v0.9.0 (2022-08-11) ### Features - Intial support for binary_sensor devices ([#12](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/12), [`fc0ff14`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/fc0ff143d695beedd190cc002e1173c8c17f5783)) ## v0.8.2 (2022-08-11) ### Bug Fixes - Set title and device name to something useful ([`73c4d4b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/73c4d4b59f6729f9806bdf85d2544698840f4198)) ### Chores - Black tests ([`09c0b0e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/09c0b0e743ddbf78c3324ad769a1d11d202e919f)) ## v0.8.1 (2022-08-11) ### Bug Fixes - Never poll if still pending ([`f617708`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f617708e4f67d0a6a4d0b853efee5568a14f8aba)) ### Chores - Lint ([`346ceae`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/346ceae4e2fed90991ab663d319e341ff9947423)) ## v0.8.0 (2022-08-06) ### Features - Support polling MiFlora battery ([#11](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/11), [`f08707b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f08707bd45d250a44ea7ca5da059feae61851089)) ## v0.7.0 (2022-08-01) ### Features - Add XMZNMSBMCN03 ([#9](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/9), [`7ff0d49`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7ff0d49f73f8150e520aaa77868a4f1bd65363b1)) ## v0.6.4 (2022-08-01) ### Bug Fixes - Refactor tests to avoid mypy failure ([`c1456d7`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c1456d7fd96300ffc7bcc21c7d17d6ec63e7a287)) - Track last full service info so that we can quickly reauth the bindkey ([`c4feb20`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c4feb20468fdfdd1a9c80c0bee77bef2a250d462)) ## v0.6.3 (2022-08-01) ### Bug Fixes - Unset bindkey_verified if bindkey starts to fail ([`d2fc9eb`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/d2fc9eb36289a0c4779e97439e682016ae0de1fc)) ## v0.6.2 (2022-07-28) ### Bug Fixes - Track whether or not we have seen a packet with a payload (HA75833) ([#8](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/8), [`c99f9a2`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c99f9a2cbd09ac6cebd31349d49d6f038bfea0e6)) ## v0.6.1 (2022-07-27) ### Bug Fixes - Voltage sensor should have a device class ([`600bb78`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/600bb7819752b6f742e515eea177b65c83b1d3dc)) ### Chores - Lint fixes ([`667239b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/667239b568d324a7f552467c040b1529f97a3ae2)) ## v0.6.0 (2022-07-25) ### Features - Add Formaldehyde, Consumable and Voltage sensor ([`8292de7`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8292de7425597897d562ea43380af221c661df90)) ## v0.5.2 (2022-07-25) ### Bug Fixes - Add special casing for when illumination is used for a binary sensor instead of a sensor ([`59c5729`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/59c5729b4c2f39944d61ab65c3aee40c6d041d21)) ## v0.5.1 (2022-07-24) ### Bug Fixes - Unset bindkey_verified on legacy devices if payload is corrupt as may be wrong key ([`f57827a`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/f57827a7caa0c9c6721f1d9773e89f3472ccf5c7)) ## v0.5.0 (2022-07-24) ### Features - New bindkey_verified variable to track whether encryption is working ([`dc622a5`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/dc622a511e1909d96d706b77ff86c8ad1905bda0)) ## v0.4.1 (2022-07-24) ### Bug Fixes - Hide encrypted devices on macOS where we don't know MAC address (for now) ([`20d0e62`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/20d0e62939907b8d58888cd346bda5f3365f943c)) ## v0.4.0 (2022-07-23) ### Features - Expose type of encryption to use on XiaomiBluetoothDeviceData ([`935482d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/935482d0a78daf10b6e2ff772d32d195eba923ea)) ## v0.3.0 (2022-07-23) ### Features - Add lux sensor to cgpr1 ([#5](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/5), [`accb30e`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/accb30eaa5dc710518e02b92bbfa1ed6c495c992)) ## v0.2.0 (2022-07-23) ### Bug Fixes - Fix test regression ([`1f80374`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/1f80374c5c56049235ee55f1fc885b8da2cadbb6)) - Remove stray print ([`55d67d7`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/55d67d741c1c7dd8da14729619515e09680542e1)) ### Chores - Fix lints ([`7511a1a`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/7511a1a435d61f246aadf497cdf5529492a24cdb)) ### Features - Add support for more sensor types ([`0a87594`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/0a875940fdf7330d53a03f3ca7b7a489353396b6)) ## v0.1.0 (2022-07-22) ### Features - Add release notes for 0.0.5 ([`2a1d8c5`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2a1d8c510e31e82bbc22d191eb98efc9a3cf65e8)) - Add SU001-T ([#2](https://github.com/Bluetooth-Devices/xiaomi-ble/pull/2), [`2281d03`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2281d033323f8e78e5c2a192c8a900efe859308b)) - Bump version ([`01656a0`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/01656a03ae6ece3bbcf361bf14355894cf359f0d)) ## v0.0.4 (2022-07-22) ### Bug Fixes - Workaround not knowing MAC on macOS ([`adcb639`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/adcb63967dbdfcd502c5fd15c27c33ceca5c7638)) ## v0.0.3 (2022-07-22) ### Bug Fixes - Trim service uuid from start of service data ([`8c35dc1`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8c35dc16cf3a782f045d567bdce26e5a3296bbf9)) ## v0.0.2 (2022-07-22) ### Bug Fixes - Re-export sensor state classes like other bluetooth helpers ([`b7b97ba`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/b7b97ba50963218d45d9d883179302aca08987ca)) ## v0.0.1 (2022-07-22) ### Bug Fixes - Get remaining tests working ([`8a042e4`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/8a042e4e2ef64151b6ad1db45ba9164d97a536cb)) - Give all tests right MAc, so validation passes on linux ([`da76a1d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/da76a1dbad06e8eb0c990355d4b84dee3db23860)) - Use fromutctimestamp for stable tests ([`c1e574c`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/c1e574c7a3e3eed98ee061764efc37599b9c251b)) ### Chores - __init__ version number ([`af8812d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/af8812d4307350fad8247a8d1aa97766b764d236)) - Fix flake8 E203 ([`595c318`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/595c31884bca7f34212d5ea2c5798b4ea55613c3)) - Fix flake8 E501 ([`986bb25`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/986bb25c2481cb5612314a05ccf4d4c8847d4085)) - More lint ([`0ef1f3b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/0ef1f3bd6c08c45eb7aa6d4362b494dacaa5400c)) - More lint ([`59c6ade`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/59c6ade0a6623683c5e7213493db9c8b30d43bd6)) - More lint ([`49e4ad5`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/49e4ad5c6c8db0d10f5bec74a51140c9b90be089)) - More lint ([`43f5d82`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/43f5d82cb0619727d8728cdb6e674e0fcf02ed8e)) - More mypy cleanup ([`2b65b45`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/2b65b4565439092c512113e2ee7ce509c08fe356)) - Reset changelog ([`bfe7793`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bfe77939cd02c26e82d338b6987df0741038e3b0)) - Reset version number ([`dec6b69`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/dec6b69f60781ec37a4fabf0352b194c0a12818d)) - Run isort ([`890b72d`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/890b72d25e0e78e14a3fa35dd4c40b837adad731)) - Spelling ([`bf2c604`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/bf2c6049df43e8256a9b28235876dc3b390740cb)) - Spelling ([`483b17b`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/483b17b0b5f7d6c7ee7502f67a2731552ba9d2d1)) - Start mypy cleanup ([`88a13e9`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/88a13e9debe3b54bc743c23fae4cc6e343f549b1)) - Upgrade sensor-state-data>=2.0 ([`59f2d00`](https://github.com/Bluetooth-Devices/xiaomi-ble/commit/59f2d0056b52238e02343c3d70d57eda3d1c2301)) Bluetooth-Devices-xiaomi-ble-b47183c/CONTRIBUTING.md0000664000175000017500000000742614772435047024031 0ustar billchenchinabillchenchina# Contributing Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. You can contribute in many ways: ## Types of Contributions ### Report Bugs Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: - Your operating system name and version. - Any details about your local setup that might be helpful in troubleshooting. - Detailed steps to reproduce the bug. ### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. ### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. ### Write Documentation Xiaomi BLE could always use more documentation, whether as part of the official Xiaomi BLE docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: - Explain in detail how it would work. - Keep the scope as narrow as possible, to make it easier to implement. - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 ## Get Started! Ready to contribute? Here's how to set yourself up for local development. 1. Fork the repo on GitHub. 2. Clone your fork locally: ```shell $ git clone git@github.com:your_name_here/xiaomi-ble.git ``` 3. Install the project dependencies with [Poetry](https://python-poetry.org): ```shell $ poetry install ``` 4. Create a branch for local development: ```shell $ git checkout -b name-of-your-bugfix-or-feature ``` Now you can make your changes locally. 5. When you're done making changes, check that your changes pass our tests: ```shell $ poetry run pytest ``` 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: ```shell $ pre-commit run -a ``` Or better, install the hooks once and have them run automatically each time you commit: ```shell $ pre-commit install ``` 7. Commit your changes and push your branch to GitHub: ```shell $ git add . $ git commit -m "feat(something): your detailed description of your changes" $ git push origin name-of-your-bugfix-or-feature ``` Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): ```shell $ gh pr create --fill ``` ## Pull Request Guidelines We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 1. Include tests for feature or bug fixes. 2. Update the documentation for significant features. 3. Ensure tests are passing on CI. ## Tips To run a subset of tests: ```shell $ pytest tests ``` ## Making a new release The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. [gh-issues]: https://github.com/bluetooth-devices/xiaomi-ble/issues Bluetooth-Devices-xiaomi-ble-b47183c/LICENSE0000664000175000017500000002612114772435047022576 0ustar billchenchinabillchenchina Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2022 J. Nick Koston Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Bluetooth-Devices-xiaomi-ble-b47183c/.editorconfig0000664000175000017500000000044414772435047024246 0ustar billchenchinabillchenchina# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.bat] indent_style = tab end_of_line = crlf [LICENSE] insert_final_newline = false [Makefile] indent_style = tab Bluetooth-Devices-xiaomi-ble-b47183c/.all-contributorsrc0000664000175000017500000000046114772435047025421 0ustar billchenchinabillchenchina{ "projectName": "xiaomi-ble", "projectOwner": "bluetooth-devices", "repoType": "github", "repoHost": "https://github.com", "files": [ "README.md" ], "imageSize": 80, "commit": true, "commitConvention": "angular", "contributors": [], "contributorsPerLine": 7, "skipCi": true } Bluetooth-Devices-xiaomi-ble-b47183c/README.md0000664000175000017500000000676214772435047023061 0ustar billchenchinabillchenchina# Xiaomi BLE

CI Status Documentation Status Test coverage percentage

Poetry black pre-commit

PyPI Version Supported Python versions License

Manage Xiaomi BLE devices ## Installation Install this via pip (or your favourite package manager). `pip install xiaomi-ble` ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ## Credits This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [browniebroke/cookiecutter-pypackage](https://github.com/browniebroke/cookiecutter-pypackage) project template. Special thanks to Piotr Machowski for the cloud key extractor basis. Bluetooth-Devices-xiaomi-ble-b47183c/.github/0000775000175000017500000000000014772435047023127 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/.github/labels.toml0000664000175000017500000000351514772435047025272 0ustar billchenchinabillchenchina[breaking] color = "ffcc00" name = "breaking" description = "Breaking change." [bug] color = "d73a4a" name = "bug" description = "Something isn't working" [dependencies] color = "0366d6" name = "dependencies" description = "Pull requests that update a dependency file" [github_actions] color = "000000" name = "github_actions" description = "Update of github actions" [documentation] color = "1bc4a5" name = "documentation" description = "Improvements or additions to documentation" [duplicate] color = "cfd3d7" name = "duplicate" description = "This issue or pull request already exists" [enhancement] color = "a2eeef" name = "enhancement" description = "New feature or request" ["good first issue"] color = "7057ff" name = "good first issue" description = "Good for newcomers" ["help wanted"] color = "008672" name = "help wanted" description = "Extra attention is needed" [invalid] color = "e4e669" name = "invalid" description = "This doesn't seem right" [nochangelog] color = "555555" name = "nochangelog" description = "Exclude pull requests from changelog" [question] color = "d876e3" name = "question" description = "Further information is requested" [removed] color = "e99695" name = "removed" description = "Removed piece of functionalities." [tests] color = "bfd4f2" name = "tests" description = "CI, CD and testing related changes" [wontfix] color = "ffffff" name = "wontfix" description = "This will not be worked on" [discussion] color = "c2e0c6" name = "discussion" description = "Some discussion around the project" [hacktoberfest] color = "ffa663" name = "hacktoberfest" description = "Good issues for Hacktoberfest" [answered] color = "0ee2b6" name = "answered" description = "Automatically closes as answered after a delay" [waiting] color = "5f7972" name = "waiting" description = "Automatically closes if no answer after a delay" Bluetooth-Devices-xiaomi-ble-b47183c/.github/ISSUE_TEMPLATE/0000775000175000017500000000000014772435047025312 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/.github/ISSUE_TEMPLATE/1-bug_report.md0000664000175000017500000000042214772435047030140 0ustar billchenchinabillchenchina--- name: Bug report about: Create a report to help us improve labels: bug --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Additional context** Add any other context about the problem here. Bluetooth-Devices-xiaomi-ble-b47183c/.github/ISSUE_TEMPLATE/2-feature-request.md0000664000175000017500000000067214772435047031121 0ustar billchenchinabillchenchina--- name: Feature request about: Suggest an idea for this project labels: enhancement --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional context** Add any other context or screenshots about the feature request here. Bluetooth-Devices-xiaomi-ble-b47183c/.github/workflows/0000775000175000017500000000000014772435047025164 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/.github/workflows/hacktoberfest.yml0000664000175000017500000000053414772435047030535 0ustar billchenchinabillchenchinaname: Hacktoberfest on: schedule: # Run every day in October - cron: "0 0 * 10 *" # Run on the 1st of November to revert - cron: "0 13 1 11 *" jobs: hacktoberfest: runs-on: ubuntu-latest steps: - uses: browniebroke/hacktoberfest-labeler-action@v2.3.0 with: github_token: ${{ secrets.GH_PAT }} Bluetooth-Devices-xiaomi-ble-b47183c/.github/workflows/issue-manager.yml0000664000175000017500000000134014772435047030445 0ustar billchenchinabillchenchinaname: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.5.1 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { "message": "Automatically closing. To re-open, please provide the additional information requested." } } Bluetooth-Devices-xiaomi-ble-b47183c/.github/workflows/ci.yml0000664000175000017500000000472714772435047026314 0ustar billchenchinabillchenchinaname: CI on: push: branches: - main pull_request: concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.10" - uses: pre-commit/action@v3.0.1 # Make sure commit messages follow the conventional commits convention: # https://www.conventionalcommits.org commitlint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v6 test: strategy: fail-fast: false matrix: python-version: - "3.10" - "3.11" - "3.12" - "3.13" os: - ubuntu-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - uses: snok/install-poetry@v1 - name: Install Dependencies shell: bash run: poetry install - name: Test with Pytest shell: bash run: poetry run pytest --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} release: runs-on: ubuntu-latest environment: release if: github.ref == 'refs/heads/main' needs: - test - lint - commitlint permissions: id-token: write contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Run semantic release: # - Update CHANGELOG.md # - Update version in code # - Create git tag # - Create GitHub release - name: Python Semantic Release id: release uses: python-semantic-release/python-semantic-release@v9.21.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 if: steps.release.outputs.released == 'true' - name: Upload Github Release Assets uses: python-semantic-release/publish-action@v9.21.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} Bluetooth-Devices-xiaomi-ble-b47183c/.github/workflows/labels.yml0000664000175000017500000000077514772435047027162 0ustar billchenchinabillchenchinaname: Sync Github labels on: push: branches: - main paths: - ".github/**" jobs: labels: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.12 - name: Install labels run: pip install labels - name: Sync config with Github run: labels -u ${{ github.repository_owner }} -t ${{ secrets.GITHUB_TOKEN }} sync -f .github/labels.toml Bluetooth-Devices-xiaomi-ble-b47183c/.github/dependabot.yml0000664000175000017500000000135114772435047025757 0ustar billchenchinabillchenchina# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" commit-message: prefix: "chore(deps-ci): " groups: github-actions: patterns: - "*" - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" Bluetooth-Devices-xiaomi-ble-b47183c/.flake80000664000175000017500000000005514772435047022742 0ustar billchenchinabillchenchina[flake8] exclude = docs max-line-length = 88 Bluetooth-Devices-xiaomi-ble-b47183c/.gitignore0000664000175000017500000000406614772435047023565 0ustar billchenchinabillchenchina# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ Bluetooth-Devices-xiaomi-ble-b47183c/.idea/0000775000175000017500000000000014772435047022547 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/.idea/workspace.xml0000664000175000017500000000673114772435047025276 0ustar billchenchinabillchenchina 1720200172152 Bluetooth-Devices-xiaomi-ble-b47183c/.idea/govee-ble.iml0000664000175000017500000000051514772435047025120 0ustar billchenchinabillchenchina Bluetooth-Devices-xiaomi-ble-b47183c/.idea/watcherTasks.xml0000664000175000017500000000525314772435047025741 0ustar billchenchinabillchenchina Bluetooth-Devices-xiaomi-ble-b47183c/tests/0000775000175000017500000000000014772435047022731 5ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/tests/test_parser.py0000664000175000017500000035513014772435047025645 0ustar billchenchinabillchenchina"""The tests for the Xiaomi ble parser.""" import logging from unittest.mock import patch import pytest from home_assistant_bluetooth import BluetoothServiceInfo from sensor_state_data import ( BinarySensorDescription, BinarySensorDeviceClass, BinarySensorValue, DeviceClass, DeviceKey, Event, SensorDescription, SensorDeviceInfo, SensorUpdate, SensorValue, Units, ) from xiaomi_ble.const import SERVICE_HHCCJCY10, SERVICE_SCALE1, SERVICE_SCALE2 from xiaomi_ble.parser import ( EncryptionScheme, ExtendedBinarySensorDeviceClass, ExtendedSensorDeviceClass, XiaomiBluetoothDeviceData, ) KEY_BATTERY = DeviceKey(key="battery", device_id=None) KEY_BINARY_DOOR = DeviceKey(key="door", device_id=None) KEY_BINARY_FINGERPRINT = DeviceKey(key="fingerprint", device_id=None) KEY_BINARY_MOTION = DeviceKey(key="motion", device_id=None) KEY_BINARY_OCCUPANCY = DeviceKey(key="occupancy", device_id=None) KEY_BINARY_LIGHT = DeviceKey(key="light", device_id=None) KEY_BINARY_LOCK = DeviceKey(key="lock", device_id=None) KEY_BINARY_OPENING = DeviceKey(key="opening", device_id=None) KEY_BINARY_DOOR_LEFT_OPEN = DeviceKey(key="door_left_open", device_id=None) KEY_BINARY_DEVICE_FORCIBLY_REMOVED = DeviceKey( key="device_forcibly_removed", device_id=None ) KEY_BINARY_PRY_THE_DOOR = DeviceKey(key="pry_the_door", device_id=None) KEY_BINARY_TOOTHBRUSH = DeviceKey(key="toothbrush", device_id=None) KEY_CONDUCTIVITY = DeviceKey(key="conductivity", device_id=None) KEY_COUNTER = DeviceKey(key="counter", device_id=None) KEY_EVENT_BUTTON = DeviceKey(key="button", device_id=None) KEY_EVENT_CUBE = DeviceKey(key="cube", device_id=None) KEY_EVENT_DIMMER = DeviceKey(key="dimmer", device_id=None) KEY_EVENT_FINGERPRINT = DeviceKey(key="fingerprint", device_id=None) KEY_EVENT_MOTION = DeviceKey(key="motion", device_id=None) KEY_HUMIDITY = DeviceKey(key="humidity", device_id=None) KEY_ILLUMINANCE = DeviceKey(key="illuminance", device_id=None) KEY_IMPEDANCE = DeviceKey(key="impedance", device_id=None) KEY_KEY_ID = DeviceKey(key="key_id", device_id=None) KEY_LOCK_METHOD = DeviceKey(key="lock_method", device_id=None) KEY_MASS_NON_STABILIZED = DeviceKey(key="mass_non_stabilized", device_id=None) KEY_MASS = DeviceKey(key="mass", device_id=None) KEY_MOISTURE = DeviceKey(key="moisture", device_id=None) KEY_POWER = DeviceKey(key="power", device_id=None) KEY_SCORE = DeviceKey(key="score", device_id=None) KEY_SIGNAL_STRENGTH = DeviceKey(key="signal_strength", device_id=None) KEY_SMOKE = DeviceKey(key="smoke", device_id=None) KEY_TEMPERATURE = DeviceKey(key="temperature", device_id=None) @pytest.fixture(autouse=True) def logging_config(caplog): caplog.set_level(logging.DEBUG) @pytest.fixture(autouse=True) def mock_platform(): with patch("sys.platform") as p: p.return_value = "linux" yield p def bytes_to_service_info( payload: bytes, address: str = "00:00:00:00:00:00" ) -> BluetoothServiceInfo: return BluetoothServiceInfo( name="Test", address=address, rssi=-60, manufacturer_data={}, service_data={"0000fe95-0000-1000-8000-00805f9b34fb": payload}, service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"], source="", ) def test_blank_advertisements_then_encrypted(): """Test that we can reject empty payloads.""" device = XiaomiBluetoothDeviceData() # First advertisement has a header but no payload, so we can't tell # if it has encryption data_string = b"0X[\x05\x02H<\xd48\xc1\xa4\x08" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:D4:3C:48") assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.NONE assert device.pending is True assert device.last_service_info is None # Second advertisement has encryption data_string = b"XX[\x05\x01H<\xd48\xc1\xa4\x9c\xf2U\xcf\xdd\x00\x00\x00/\xae/\xf2" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:D4:3C:48") device.update(advertisement) assert device.encryption_scheme == EncryptionScheme.MIBEACON_4_5 assert device.pending is False # type: ignore def test_blank_advertisements_then_unencrypted(): """Test that we can reject empty payloads.""" # NOTE: THIS IS SYNTHETIC TEST DATA - i took a known unencrypted device and flipped # frctrl_object_include, then truncated it to not include the data payload device = XiaomiBluetoothDeviceData() # First advertisement has a header but no payload, so we can't tell # if it has encryption data_string = b"1 \x98\x00\x12\xf3Ok\x8d|\xc4\r" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6B:4F:F3") assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.NONE assert device.pending is True # Second advertisement has encryption data_string = b"q \x98\x00\x12\xf3Ok\x8d|\xc4\r\x04\x10\x02\xc4\x00" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6B:4F:F3") device.update(advertisement) assert device.encryption_scheme == EncryptionScheme.NONE assert device.pending is False def test_blank_advertisements_then_encrypted_last_service_info(): """Test that we can capture valid service info records""" device = XiaomiBluetoothDeviceData() # First advertisement has a header but no payload, so we can't tell # if it has encryption data_string = b"0X[\x05\x02H<\xd48\xc1\xa4\x08" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:D4:3C:48") assert device.supported(advertisement) assert device.last_service_info is None # Second advertisement has encryption data_string = b"XX[\x05\x01H<\xd48\xc1\xa4\x9c\xf2U\xcf\xdd\x00\x00\x00/\xae/\xf2" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:D4:3C:48") device.update(advertisement) assert device.last_service_info == advertisement def test_blank_advertisements_then_unencrypted_last_service_info(): """Test that we can capture valid service info records.""" # NOTE: THIS IS SYNTHETIC TEST DATA - i took a known unencrypted device and flipped # frctrl_object_include, then truncated it to not include the data payload device = XiaomiBluetoothDeviceData() # First advertisement has a header but no payload, so we can't tell # if it has encryption data_string = b"1 \x98\x00\x12\xf3Ok\x8d|\xc4\r" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6B:4F:F3") assert device.supported(advertisement) assert device.last_service_info is None # Second advertisement has encryption data_string = b"q \x98\x00\x12\xf3Ok\x8d|\xc4\r\x04\x10\x02\xc4\x00" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6B:4F:F3") device.update(advertisement) assert device.last_service_info == advertisement def test_encryption_needs_v2(): """Test that we can detect what kind of encryption key a device needs.""" data_string = b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY assert not device.bindkey_verified def test_encryption_needs_v5(): """Test that we can detect what kind of encryption key a device needs.""" data_string = b"XXH\x0bh_\x124-XZ\x0b\x18A\xe2\xaa\x00\x0e\x00\xa4\x96O\xb5" advertisement = bytes_to_service_info(data_string, address="5A:58:2D:34:12:5F") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.MIBEACON_4_5 assert not device.bindkey_verified def test_bindkey_wrong(): """Test Xiaomi parser for RTCGQ02LM with wrong encryption key.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99" data_string = ( b"XY\x8d\n\x17\x0f\xc4\xe0D\xefT|" b"\xc2z\\\x03\xa1\x00\x00\x00y\r\xf2X" ) advertisement = bytes_to_service_info(data_string, address="54:EF:44:E0:C4:0F") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert not device.bindkey_verified assert device.decryption_failed assert device.update(advertisement) == SensorUpdate( title="Motion Sensor C40F (RTCGQ02LM)", devices={ None: SensorDeviceInfo( name="Motion Sensor C40F", manufacturer="Xiaomi", model="RTCGQ02LM", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_bindkey_verified_can_be_unset_v4(): """Test Xiaomi parser for RTCGQ02LM with wrong encryption key.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99" data_string = ( b"XY\x8d\n\x17\x0f\xc4\xe0D\xefT|" b"\xc2z\\\x03\xa1\x00\x00\x00y\r\xf2X" ) advertisement = bytes_to_service_info(data_string, address="54:EF:44:E0:C4:0F") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) device.bindkey_verified = True device.decryption_failed = False assert device.supported(advertisement) # the first advertisement will fail decryption, but we don't ask to reauth yet assert device.bindkey_verified assert device.decryption_failed data_string = b"XY\x8d\n\x18\x0f\xc4\xe0D\xefT|\xc2z\\\x03\xa1\x00\x00\x00y" advertisement = bytes_to_service_info(data_string, address="54:EF:44:E0:C4:0F") assert device.supported(advertisement) # the second advertisement will fail decryption again, but now we ask to reauth assert device.decryption_failed assert not device.bindkey_verified def test_bindkey_wrong_legacy(): """Test Xiaomi parser for YLKG07YL with wrong encryption key.""" bindkey = "b853075158487aa39a5b5ea9" data_string = b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert not device.bindkey_verified assert device.decryption_failed assert device.update(advertisement) == SensorUpdate( title="Dimmer Switch 988B (YLKG07YL/YLKG08YL)", devices={ None: SensorDeviceInfo( name="Dimmer Switch 988B", manufacturer="Xiaomi", model="YLKG07YL/YLKG08YL", hw_version=None, sw_version="Xiaomi (MiBeacon V3 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) assert device.unhandled == {} def test_bindkey_verified_can_be_unset_legacy(): """Test Xiaomi parser for YLKG07YL with wrong encryption key.""" bindkey = "b853075158487aa39a5b5ea9" data_string = b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) device.bindkey_verified = True device.decryption_failed = False assert device.supported(advertisement) # the first advertisement will fail decryption, but we don't ask to reauth yet assert device.bindkey_verified assert device.decryption_failed data_string = b"X0\xb6\x03\xd3\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") assert device.supported(advertisement) # the second advertisement will fail decryption again, but now we ask to reauth assert device.decryption_failed assert not device.bindkey_verified def test_Xiaomi_LYWSDCGQ(caplog): """Test Xiaomi parser for LYWSDCGQ.""" data_string = b"P \xaa\x01\xda!\x9354-X\r\x10\x04\xfe\x00H\x02" advertisement = bytes_to_service_info(data_string, address="58:2D:34:35:93:21") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert not device.sleepy_device assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 9321 (LYWSDCGQ)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 9321", manufacturer="Xiaomi", model="LYWSDCGQ", sw_version="Xiaomi (MiBeacon V2)", hw_version=None, ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.4 ), KEY_HUMIDITY: SensorValue( device_key=KEY_HUMIDITY, name="Humidity", native_value=58.4 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_CGG1(): """Test Xiaomi parser for CGG1.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99" data_string = b"XXH\x0bh_\x124-XZ\x0b\x18A\xe2\xaa\x00\x0e\x00\xa4\x96O\xb5" advertisement = bytes_to_service_info(data_string, address="5A:58:2D:34:12:5F") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 125F (CGG1-ENCRYPTED)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 125F", manufacturer="Xiaomi", model="CGG1-ENCRYPTED", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=59.6 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_CGDK2(): """Test Xiaomi parser for CGDK2.""" data_string = b"XXo\x06\x07\x89 \x124-X_\x17m\xd5O\x02\x00\x00/\xa4S\xfa" bindkey = "a3bfe9853dd85a620debe3620caaa351" advertisement = bytes_to_service_info(data_string, address="58:2D:34:12:20:89") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 2089 (CGDK2)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 2089", manufacturer="Xiaomi", model="CGDK2", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=22.6 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_LYWSD02(): """Test Xiaomi parser for LYWSD02.""" def test_Xiaomi_LYWSD03MMC(): """Test Xiaomi parser for LYWSD03MMC without encryption.""" data_string = b"P0[\x05\x03L\x94\xb48\xc1\xa4\r\x10\x04\x10\x01\xea\x01" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:B4:94:4C") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 944C (LYWSD03MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 944C", manufacturer="Xiaomi", model="LYWSD03MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V3)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=27.2 ), KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=49.0 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_LYWSD02MMC(): """Test Xiaomi parser for LYWSD02MMC.""" bindkey = "a115210eed7a88e50ad52662e732a9fb" data_string = b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:56:53:84") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 5384 (LYWSD02MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 5384", manufacturer="Xiaomi", model="LYWSD02MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=58 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_LYWSD02MMC_v2_temperature(): """Test Xiaomi parser for LYWSD02MMC updated version temperature.""" bindkey = "19b1c678ab0a8bc3dc77765f059188d4" data_string = b'HXB%) -\x8czv\xb7V\xa8*\x00x\xb8"N' advertisement = bytes_to_service_info(data_string, address="A4:C1:38:0E:FD:78") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor FD78 (LYWSD02MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor FD78", manufacturer="Xiaomi", model="LYWSD02MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=26.10 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_LYWSD02MMC_v2_humidity(): """Test Xiaomi parser for LYWSD02MMC updated version humidity.""" bindkey = "19b1c678ab0a8bc3dc77765f059188d4" data_string = b"XXB%\x88x\xfd\x0e8\xc1\xa4\x05\xf6S\x8a\xa7*\x00b\xb1\xa9f" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:0E:FD:78") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor FD78 (LYWSD02MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor FD78", manufacturer="Xiaomi", model="LYWSD02MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=38 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_LYWSD03MMC_encrypted(): """Test Xiaomi parser for LYWSD03MMC with encryption.""" data_string = ( b"XX[\x05P\xf4\x83\x028" b"\xc1\xa4\x95\xefXv<&\x00\x00\x97\xe2\xab\xb5" ) bindkey = "e9ea895fac7cca6d30532432a516f3a8" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:02:83:F4") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 83F4 (LYWSD03MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 83F4", manufacturer="Xiaomi", model="LYWSD03MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=46 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_XMWSDJ04MMC(): """Test Xiaomi parser for XMWSDJ04MMC with encryption.""" bindkey = "b2cf9a553d53571b5657defd582d676e" data_string = b"HY\x03\x12\xa4\x1bwn|\x96\xad\xd7\x00\x00\x00\xf2\xbfT[" advertisement = bytes_to_service_info(data_string, address="2C:11:65:25:70:04") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Thermometer 7004 (XMWSDJ04MMC)", devices={ None: SensorDeviceInfo( name="Thermometer 7004", manufacturer="Xiaomi", model="XMWSDJ04MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=45.0 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_XMMF01JQD(): """Test Xiaomi parser for XMMF01JQD.""" data_string = b"P0\xe1\x04\x8eT\xd3\xe60S\xe2\x01\x10\x03\x01\x00\x00" advertisement = bytes_to_service_info(data_string, address="E2:53:30:E6:D3:54") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Magic Cube D354 (XMMF01JQD)", devices={ None: SensorDeviceInfo( name="Magic Cube D354", manufacturer="Xiaomi", model="XMMF01JQD", hw_version=None, sw_version="Xiaomi (MiBeacon V3)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_CUBE: Event( device_key=KEY_EVENT_CUBE, name="Cube", event_type="rotate_left", event_properties=None, ), }, ) def test_Xiaomi_CGC1(): """Test Xiaomi parser for CGC1.""" def test_Xiaomi_CGD1(): """Test Xiaomi parser for CGD1.""" def test_Xiaomi_CGP1W(): """Test Xiaomi parser for CGP1W.""" def test_Xiaomi_MHO_C303(): """Test Xiaomi parser for MHO-C303.""" def test_Xiaomi_MHO_C401(): """Test Xiaomi parser for MHO-C401.""" def test_Xiaomi_JQJCY01YM1(): """Test Xiaomi parser for JQJCY01YM.""" def test_Xiaomi_JTYJGD03MI_smoke(): """Test Xiaomi parser for JTYJGD03MI.""" bindkey = "5b51a7c91cde6707c9ef18dfda143a58" data_string = ( b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90" ) advertisement = bytes_to_service_info(data_string, address="54:EF:44:E3:9C:BC") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Smoke Detector 9CBC (JTYJGD03MI)", devices={ None: SensorDeviceInfo( name="Smoke Detector 9CBC", manufacturer="Xiaomi", model="JTYJGD03MI", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_SMOKE: BinarySensorDescription( device_key=KEY_SMOKE, device_class=BinarySensorDeviceClass.SMOKE, ), }, binary_entity_values={ KEY_SMOKE: BinarySensorValue( name="Smoke", device_key=KEY_SMOKE, native_value=True ), }, ) def test_Xiaomi_JTYJGD03MI_press(): """Test Xiaomi parser for JTYJGD03MI.""" bindkey = "5b51a7c91cde6707c9ef18dfda143a58" data_string = ( b'XY\x97\td\xbc\x9c\xe3D\xefT" `' b"\x88\xfd\x00\x00\x00\x00:\x14\x8f\xb3" ) advertisement = bytes_to_service_info(data_string, address="54:EF:44:E3:9C:BC") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Smoke Detector 9CBC (JTYJGD03MI)", devices={ None: SensorDeviceInfo( name="Smoke Detector 9CBC", manufacturer="Xiaomi", model="JTYJGD03MI", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_BUTTON: Event( device_key=KEY_EVENT_BUTTON, name="Button", event_type="press", event_properties=None, ), }, ) def test_Xiaomi_HHCCJCY01(): """Test Xiaomi parser for HHCCJCY01.""" data_string = b"q \x98\x00\x12\xf3Ok\x8d|\xc4\r\x04\x10\x02\xc4\x00" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6B:4F:F3") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Plant Sensor 4FF3 (HHCCJCY01)", devices={ None: SensorDeviceInfo( name="Plant Sensor 4FF3", manufacturer="Xiaomi", model="HHCCJCY01", hw_version=None, sw_version="Xiaomi (MiBeacon V2)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=19.6 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_HHCCJCY01_all_values(): """Test Xiaomi parser for HHCCJCY01.""" device = XiaomiBluetoothDeviceData() device.update( bytes_to_service_info( b"q \x98\x00fz>j\x8d|\xc4\r\x07\x10\x03\x00\x00\x00", address="C4:7C:8D:6A:3E:7A", ) ) device.update( bytes_to_service_info( b"q \x98\x00hz>j\x8d|\xc4\r\t\x10\x02W\x02", address="C4:7C:8D:6A:3E:7A" ) ) device.update( bytes_to_service_info( b"q \x98\x00Gz>j\x8d|\xc4\r\x08\x10\x01@", address="C4:7C:8D:6A:3E:7A" ) ) assert device.update( bytes_to_service_info( b"q \x98\x00iz>j\x8d|\xc4\r\x04\x10\x02\xf4\x00", address="C4:7C:8D:6A:3E:7A", ) ) == SensorUpdate( title="Plant Sensor 3E7A (HHCCJCY01)", devices={ None: SensorDeviceInfo( name="Plant Sensor 3E7A", manufacturer="Xiaomi", model="HHCCJCY01", hw_version=None, sw_version="Xiaomi (MiBeacon V2)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement=Units.LIGHT_LUX, ), KEY_CONDUCTIVITY: SensorDescription( device_key=KEY_CONDUCTIVITY, device_class=DeviceClass.CONDUCTIVITY, native_unit_of_measurement=Units.CONDUCTIVITY, ), KEY_MOISTURE: SensorDescription( device_key=KEY_MOISTURE, device_class=DeviceClass.MOISTURE, native_unit_of_measurement=Units.PERCENTAGE, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=24.4 ), KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=0 ), KEY_CONDUCTIVITY: SensorValue( name="Conductivity", device_key=KEY_CONDUCTIVITY, native_value=599 ), KEY_MOISTURE: SensorValue( name="Moisture", device_key=KEY_MOISTURE, native_value=64 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_HHCCJCY10(): """Test Xiaomi parser for HHCCJCY10.""" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="Test", address="DC:23:4D:E5:5B:FC", rssi=-60, manufacturer_data={}, service_data={SERVICE_HHCCJCY10: b"\x0e\x00n\x014\xa4(\x00["}, service_uuids=[SERVICE_HHCCJCY10], source="", ) ) == SensorUpdate( title="Plant Sensor 5BFC (HHCCJCY10)", devices={ None: SensorDeviceInfo( name="Plant Sensor 5BFC", manufacturer="HHCC Plant Technology Co. Ltd", model="HHCCJCY10", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement=Units.LIGHT_LUX, ), KEY_CONDUCTIVITY: SensorDescription( device_key=KEY_CONDUCTIVITY, device_class=DeviceClass.CONDUCTIVITY, native_unit_of_measurement=Units.CONDUCTIVITY, ), KEY_MOISTURE: SensorDescription( device_key=KEY_MOISTURE, device_class=DeviceClass.MOISTURE, native_unit_of_measurement=Units.PERCENTAGE, ), KEY_BATTERY: SensorDescription( device_key=KEY_BATTERY, device_class=DeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=11.0 ), KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=79012 ), KEY_CONDUCTIVITY: SensorValue( name="Conductivity", device_key=KEY_CONDUCTIVITY, native_value=91 ), KEY_MOISTURE: SensorValue( name="Moisture", device_key=KEY_MOISTURE, native_value=14 ), KEY_BATTERY: SensorValue( name="Battery", device_key=KEY_BATTERY, native_value=40 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_Scale1(): """Test Xiaomi parser for Mi Smart Scale (MiScale V1)""" data_string = b"\x22\x9e\x43\xe5\x07\x04\x0b\x10\x13\x01" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="MISCA", address="50:FB:19:1B:B5:DC", rssi=-60, manufacturer_data={}, service_data={SERVICE_SCALE1: data_string}, service_uuids=[SERVICE_SCALE1], source="", ) ) == SensorUpdate( title="Mi Smart Scale (B5DC)", devices={ None: SensorDeviceInfo( name="Mi Smart Scale (B5DC)", manufacturer="Xiaomi", model="XMTZC01HM/XMTZC04HM", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_MASS_NON_STABILIZED: SensorDescription( device_key=KEY_MASS_NON_STABILIZED, device_class=DeviceClass.MASS_NON_STABILIZED, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_MASS: SensorDescription( device_key=KEY_MASS, device_class=DeviceClass.MASS, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_MASS_NON_STABILIZED: SensorValue( name="Mass Non Stabilized", device_key=KEY_MASS_NON_STABILIZED, native_value=86.55, ), KEY_MASS: SensorValue( name="Mass", device_key=KEY_MASS, native_value=86.55, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_Scale1_mass_removed(): """Test Xiaomi parser for Mi Smart Scale (MiScale V1) mass removed""" data_string = b"\xa2 D\xb2\x07\x01\x01\n\x1a\x15" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="MISCA", address="50:FB:19:1B:B5:DC", rssi=-60, manufacturer_data={}, service_data={SERVICE_SCALE1: data_string}, service_uuids=[SERVICE_SCALE1], source="", ) ) == SensorUpdate( title="Mi Smart Scale (B5DC)", devices={ None: SensorDeviceInfo( name="Mi Smart Scale (B5DC)", manufacturer="Xiaomi", model="XMTZC01HM/XMTZC04HM", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_MASS_NON_STABILIZED: SensorDescription( device_key=KEY_MASS_NON_STABILIZED, device_class=DeviceClass.MASS_NON_STABILIZED, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_MASS_NON_STABILIZED: SensorValue( name="Mass Non Stabilized", device_key=KEY_MASS_NON_STABILIZED, native_value=87.2, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_Scale1_non_stabilized(): """Test Xiaomi parser for Mi Smart Scale (MiScale V1) non stabilized""" data_string = b"\x82\x14\x00\xe5\x07\x04\x0b\x10\x17\x08" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="MISCA", address="50:FB:19:1B:B5:DC", rssi=-60, manufacturer_data={}, service_data={SERVICE_SCALE1: data_string}, service_uuids=[SERVICE_SCALE1], source="", ) ) == SensorUpdate( title="Mi Smart Scale (B5DC)", devices={ None: SensorDeviceInfo( name="Mi Smart Scale (B5DC)", manufacturer="Xiaomi", model="XMTZC01HM/XMTZC04HM", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_MASS_NON_STABILIZED: SensorDescription( device_key=KEY_MASS_NON_STABILIZED, device_class=DeviceClass.MASS_NON_STABILIZED, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_MASS_NON_STABILIZED: SensorValue( name="Mass Non Stabilized", device_key=KEY_MASS_NON_STABILIZED, native_value=0.1, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_Scale2(): """Test Xiaomi parser for Mi Body Composition Scale (MiScale V2)""" data_string = b"\x02&\xb2\x07\x05\x04\x0f\x02\x01\xac\x01\x86B" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="MIBFS", address="50:FB:19:1B:B5:DC", rssi=-60, manufacturer_data={}, service_data={SERVICE_SCALE2: data_string}, service_uuids=[SERVICE_SCALE2], source="", ) ) == SensorUpdate( title="Mi Body Composition Scale (B5DC)", devices={ None: SensorDeviceInfo( name="Mi Body Composition Scale (B5DC)", manufacturer="Xiaomi", model="XMTZC02HM/XMTZC05HM/NUN4049CN", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_MASS_NON_STABILIZED: SensorDescription( device_key=KEY_MASS_NON_STABILIZED, device_class=DeviceClass.MASS_NON_STABILIZED, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_MASS: SensorDescription( device_key=KEY_MASS, device_class=DeviceClass.MASS, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_IMPEDANCE: SensorDescription( device_key=KEY_IMPEDANCE, device_class=DeviceClass.IMPEDANCE, native_unit_of_measurement=Units.OHM, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_MASS_NON_STABILIZED: SensorValue( name="Mass Non Stabilized", device_key=KEY_MASS_NON_STABILIZED, native_value=85.15, ), KEY_MASS: SensorValue( name="Mass", device_key=KEY_MASS, native_value=85.15, ), KEY_IMPEDANCE: SensorValue( name="Impedance", device_key=KEY_IMPEDANCE, native_value=428 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_Scale2_non_stabilized(): """Test Xiaomi parser for Mi Body Composition Scale (MiScale v2) (non stabilized)""" data_string = b"\x02\x04\xb2\x07\x01\x01\x12\x10\x1a\x00\x00\xa8R" device = XiaomiBluetoothDeviceData() assert device.update( BluetoothServiceInfo( name="MIBFS", address="50:FB:19:1B:B5:DC", rssi=-60, manufacturer_data={}, service_data={SERVICE_SCALE2: data_string}, service_uuids=[SERVICE_SCALE2], source="", ) ) == SensorUpdate( title="Mi Body Composition Scale (B5DC)", devices={ None: SensorDeviceInfo( name="Mi Body Composition Scale (B5DC)", manufacturer="Xiaomi", model="XMTZC02HM/XMTZC05HM/NUN4049CN", hw_version=None, sw_version=None, ) }, entity_descriptions={ KEY_MASS_NON_STABILIZED: SensorDescription( device_key=KEY_MASS_NON_STABILIZED, device_class=DeviceClass.MASS_NON_STABILIZED, native_unit_of_measurement=Units.MASS_KILOGRAMS, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), }, entity_values={ KEY_MASS_NON_STABILIZED: SensorValue( name="Mass Non Stabilized", device_key=KEY_MASS_NON_STABILIZED, native_value=105.8, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_GCLS002(): """Test Xiaomi parser for GCLS002 / HHCCJCY09.""" data_string = b"q \xbc\x03\xcd>Ym\x8d|\xc4\r\x04\x10\x02<\x01" advertisement = bytes_to_service_info(data_string, address="C4:7C:8D:6D:59:3E") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Grow Care Garden 593E (GCLS002)", devices={ None: SensorDeviceInfo( name="Grow Care Garden 593E", manufacturer="Xiaomi", model="GCLS002", hw_version=None, sw_version="Xiaomi (MiBeacon V2)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=31.6 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_HHCCPOT002(): """Test Xiaomi parser for HHCCPOT002.""" def test_Xiaomi_WX08ZM(): """Test Xiaomi parser for WX08ZM.""" def test_Xiaomi_MCCGQ02HL(): """Test Xiaomi parser for MCCGQ02HL.""" data_string = b"XX\x8b\t\xa3\xae!\x81\xec\xaa\xe4\x0e,U<\x04\x00\x00\xd2\x8aP\x0c" advertisement = bytes_to_service_info(data_string, address="E4:AA:EC:81:21:AE") bindkey = "017e52d2684779298709b117c0a75a7b" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door/Window Sensor 21AE (MCCGQ02HL)", devices={ None: SensorDeviceInfo( name="Door/Window Sensor 21AE", manufacturer="Xiaomi", model="MCCGQ02HL", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_OPENING: BinarySensorDescription( device_key=KEY_BINARY_OPENING, device_class=BinarySensorDeviceClass.OPENING, ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorDescription( device_key=KEY_BINARY_DOOR_LEFT_OPEN, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, ), }, binary_entity_values={ KEY_BINARY_OPENING: BinarySensorValue( device_key=KEY_BINARY_OPENING, name="Opening", native_value=True ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorValue( device_key=KEY_BINARY_DOOR_LEFT_OPEN, name="Door left open", native_value=True, ), }, ) def test_Xiaomi_CGH1(): """Test Xiaomi parser for CGH1.""" def test_Xiaomi_YM_K1501(): """Test Xiaomi parser for YM-K1501.""" def test_Xiaomi_V_SK152(): """Test Xiaomi parser for V-SK152.""" def test_Xiaomi_SJWS01LM(): """Test Xiaomi parser for SJWS01LM.""" def test_Xiaomi_MJWSD06MMC_temperature(): """Test Xiaomi parser for MJWSD06MMC without encryption.""" data_string = b"HY\xb5U:\x86\x99\xbd\xa0SD\x8f\x12\x00\x00[\x04mj" bindkey = "4d8f1373fb4d3bab557d0ebd1c78f8c4" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:80:15:07") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 1507 (MJWSD06MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 1507", manufacturer="Xiaomi", model="MJWSD06MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=25.2 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_MJWSD06MMC_humidity(): """Test Xiaomi parser for MJWSD06MMC with encryption.""" data_string = b"XY\xb5U4\x07\x15\x808\xc1\xa4\xbc\xc72\x98\x0e\x00\x00f\x96\x0f\x10" bindkey = "4d8f1373fb4d3bab557d0ebd1c78f8c4" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:80:15:07") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Temperature/Humidity Sensor 1507 (MJWSD06MMC)", devices={ None: SensorDeviceInfo( name="Temperature/Humidity Sensor 1507", manufacturer="Xiaomi", model="MJWSD06MMC", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_HUMIDITY: SensorDescription( device_key=KEY_HUMIDITY, device_class=DeviceClass.HUMIDITY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_HUMIDITY: SensorValue( name="Humidity", device_key=KEY_HUMIDITY, native_value=39 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_MJYD02YL(): """Test Xiaomi parser for MJYD02YL.""" def test_Xiaomi_MUE4094RT(): """Test Xiaomi parser for MUE4094RT.""" # MUE4094RT only sends motion detected as an event. data_string = b"@0\xdd\x03$\x03\x00\x01\x01" advertisement = bytes_to_service_info(data_string, address="DE:70:E8:B2:39:0C") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Nightlight 390C (MUE4094RT)", devices={ None: SensorDeviceInfo( name="Nightlight 390C", manufacturer="Xiaomi", model="MUE4094RT", hw_version=None, sw_version="Xiaomi (MiBeacon V3)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_MOTION: Event( device_key=KEY_EVENT_MOTION, name="Motion", event_type="motion_detected", event_properties=None, ), }, ) def test_Xiaomi_CGPR1(): """Test Xiaomi parser for CGPR1.""" def test_Xiaomi_MMC_T201_1(): """Test Xiaomi parser for MMC-T201-1.""" data_string = b'p"\xdb\x00o\xc1o\xdd\xf9\x81\x00\t\x00 \x05\xc6\rc\rQ' advertisement = bytes_to_service_info(data_string, address="00:81:F9:DD:6F:C1") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Baby Thermometer 6FC1 (MMC-T201-1)", devices={ None: SensorDeviceInfo( name="Baby Thermometer 6FC1", manufacturer="Xiaomi", model="MMC-T201-1", hw_version=None, sw_version="Xiaomi (MiBeacon V2)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_BATTERY: SensorDescription( device_key=KEY_BATTERY, device_class=DeviceClass.BATTERY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_BATTERY: SensorValue( name="Battery", device_key=KEY_BATTERY, native_value=81 ), KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=36.87199806168224, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_MMC_W505(): """Test Xiaomi parser for MMC-W505.""" data_string = b'p"\x91\x03\x0f\xdb\xabS\x18$\xd0\t\n\x00\x02u\r\x07' advertisement = bytes_to_service_info(data_string, address="D0:24:18:53:AB:DB") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Body Thermometer ABDB (MMC-W505)", devices={ None: SensorDeviceInfo( name="Body Thermometer ABDB", manufacturer="Xiaomi", model="MMC-W505", hw_version=None, sw_version="Xiaomi (MiBeacon V2)", ) }, entity_descriptions={ KEY_TEMPERATURE: SensorDescription( device_key=KEY_TEMPERATURE, device_class=DeviceClass.TEMPERATURE, native_unit_of_measurement="°C", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_TEMPERATURE: SensorValue( name="Temperature", device_key=KEY_TEMPERATURE, native_value=34.45, ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_M1S_T500(): """Test Xiaomi parser for M1S-T500.""" data_string = b"q0\x89\x047\x11[\x17Cq\xe6\t\x10\x00\x02\x00\x03" advertisement = bytes_to_service_info(data_string, address="E6:71:43:17:5B:11") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Smart Toothbrush 5B11 (M1S-T500)", devices={ None: SensorDeviceInfo( name="Smart Toothbrush 5B11", manufacturer="Xiaomi", model="M1S-T500", hw_version=None, sw_version="Xiaomi (MiBeacon V3)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_COUNTER: SensorDescription( device_key=KEY_COUNTER, device_class=ExtendedSensorDeviceClass.COUNTER, native_unit_of_measurement="s", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_COUNTER: SensorValue( name="Counter", device_key=KEY_COUNTER, native_value=3 ), }, binary_entity_descriptions={ KEY_BINARY_TOOTHBRUSH: BinarySensorDescription( device_key=KEY_BINARY_TOOTHBRUSH, device_class=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, ), }, binary_entity_values={ KEY_BINARY_TOOTHBRUSH: BinarySensorValue( device_key=KEY_BINARY_TOOTHBRUSH, name="Toothbrush", native_value=True ), }, ) def test_Xiaomi_T700(): """Test Xiaomi parser for T700.""" bindkey = "1330b99cded13258acc391627e9771f7" data_string = ( b"\x48\x58\x06\x08\xc9H\x0e\xf1\x12\x81\x07\x973\xfc\x14\x00\x00VD\xdbA" ) advertisement = bytes_to_service_info(data_string, address="ED:DE:34:3F:48:0C") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Smart Toothbrush 480C (T700)", devices={ None: SensorDeviceInfo( name="Smart Toothbrush 480C", manufacturer="Xiaomi", model="T700", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_SCORE: SensorDescription( device_key=KEY_SCORE, device_class=ExtendedSensorDeviceClass.SCORE, ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_SCORE: SensorValue(name="Score", device_key=KEY_SCORE, native_value=83), }, binary_entity_descriptions={ KEY_BINARY_TOOTHBRUSH: BinarySensorDescription( device_key=KEY_BINARY_TOOTHBRUSH, device_class=ExtendedBinarySensorDeviceClass.TOOTHBRUSH, ), }, binary_entity_values={ KEY_BINARY_TOOTHBRUSH: BinarySensorValue( device_key=KEY_BINARY_TOOTHBRUSH, name="Toothbrush", native_value=False ), }, ) def test_Xiaomi_ZNMS16LM_fingerprint(): """Test Xiaomi parser for ZNMS16LM.""" data_string = ( b"PD\x9e\x06B\x91\x8a\xebD\x1f\xd7" b"\x06\x00\x05\xff\xff\xff\xff\x00" ) advertisement = bytes_to_service_info(data_string, address="D7:1F:44:EB:8A:91") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock 8A91 (ZNMS16LM)", devices={ None: SensorDeviceInfo( name="Door Lock 8A91", manufacturer="Xiaomi", model="ZNMS16LM", hw_version=None, sw_version="Xiaomi (MiBeacon V4)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_KEY_ID: SensorDescription( device_key=KEY_KEY_ID, device_class=ExtendedSensorDeviceClass.KEY_ID, ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_KEY_ID: SensorValue( name="Key id", device_key=KEY_KEY_ID, native_value="unknown operator" ), }, binary_entity_descriptions={ KEY_BINARY_FINGERPRINT: BinarySensorDescription( device_key=KEY_BINARY_FINGERPRINT, device_class=ExtendedBinarySensorDeviceClass.FINGERPRINT, ), }, binary_entity_values={ KEY_BINARY_FINGERPRINT: BinarySensorValue( device_key=KEY_BINARY_FINGERPRINT, name="Fingerprint", native_value=True ), }, events={ KEY_EVENT_FINGERPRINT: Event( device_key=KEY_EVENT_FINGERPRINT, name="Fingerprint", event_type="match_successful", event_properties=None, ), }, ) def test_Xiaomi_ZNMS16LM_lock(): """Test Xiaomi parser for ZNMS16LM.""" data_string = b"PD\x9e\x06C\x91\x8a\xebD\x1f\xd7\x0b\x00\t" b" \x02\x00\x01\x80|D/a" advertisement = bytes_to_service_info(data_string, address="D7:1F:44:EB:8A:91") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock 8A91 (ZNMS16LM)", devices={ None: SensorDeviceInfo( name="Door Lock 8A91", manufacturer="Xiaomi", model="ZNMS16LM", hw_version=None, sw_version="Xiaomi (MiBeacon V4)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_LOCK_METHOD: SensorDescription( device_key=KEY_LOCK_METHOD, device_class=ExtendedSensorDeviceClass.LOCK_METHOD, ), KEY_KEY_ID: SensorDescription( device_key=KEY_KEY_ID, device_class=ExtendedSensorDeviceClass.KEY_ID, ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_LOCK_METHOD: SensorValue( name="Lock method", device_key=KEY_LOCK_METHOD, native_value="biometrics", ), KEY_KEY_ID: SensorValue( name="Key id", device_key=KEY_KEY_ID, native_value="Fingerprint key id 2", ), }, binary_entity_descriptions={ KEY_BINARY_LOCK: BinarySensorDescription( device_key=KEY_BINARY_LOCK, device_class=BinarySensorDeviceClass.LOCK, ), }, binary_entity_values={ KEY_BINARY_LOCK: BinarySensorValue( device_key=KEY_BINARY_LOCK, name="Lock", native_value=True ), }, events={ DeviceKey(key="lock", device_id=None): Event( device_key=DeviceKey(key="lock", device_id=None), name="Lock", event_type="unlock_outside_the_door", event_properties=None, ), }, ) def test_Xiaomi_Lockin_SV40_lock(): """Test Xiaomi parser for Locking SV40.""" bindkey = "54d84797cb77f9538b224b305c877d1e" data_string = ( b"\x48\x55\xc2\x11\x16\x50\x68\xb6\xfe\x3c\x87" b"\x80\x95\xc8\xa5\x83\x4f\x00\x00\x00\x46\x32\x21\xc6" ) advertisement = bytes_to_service_info(data_string, address="98:0C:33:A3:04:3D") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock 043D (Lockin-SV40)", devices={ None: SensorDeviceInfo( name="Door Lock 043D", manufacturer="Xiaomi", model="Lockin-SV40", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_LOCK_METHOD: SensorDescription( device_key=KEY_LOCK_METHOD, device_class=ExtendedSensorDeviceClass.LOCK_METHOD, ), KEY_KEY_ID: SensorDescription( device_key=KEY_KEY_ID, device_class=ExtendedSensorDeviceClass.KEY_ID, ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_LOCK_METHOD: SensorValue( name="Lock method", device_key=KEY_LOCK_METHOD, native_value="automatic" ), KEY_KEY_ID: SensorValue( name="Key id", device_key=KEY_KEY_ID, native_value="administrator" ), }, binary_entity_descriptions={ KEY_BINARY_LOCK: BinarySensorDescription( device_key=KEY_BINARY_LOCK, device_class=BinarySensorDeviceClass.LOCK, ), }, binary_entity_values={ KEY_BINARY_LOCK: BinarySensorValue( device_key=KEY_BINARY_LOCK, name="Lock", native_value=True ), }, events={ DeviceKey(key="lock", device_id=None): Event( device_key=DeviceKey(key="lock", device_id=None), name="Lock", event_type="unlock_inside_the_door", event_properties=None, ), }, ) def test_Xiaomi_Lockin_SV40_door(): """Test Xiaomi parser for Locking SV40.""" bindkey = "54d84797cb77f9538b224b305c877d1e" data_string = ( b"\x48\x55\xc2\x11\x14\x4e\x28\x70\x32" b"\x76\xfc\xcd\x3d\x00\x00\x00\x80\xe7\x22\x80" ) advertisement = bytes_to_service_info(data_string, address="98:0C:33:A3:04:3D") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock 043D (Lockin-SV40)", devices={ None: SensorDeviceInfo( name="Door Lock 043D", manufacturer="Xiaomi", model="Lockin-SV40", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_DOOR: BinarySensorDescription( device_key=KEY_BINARY_DOOR, device_class=BinarySensorDeviceClass.DOOR, ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorDescription( device_key=KEY_BINARY_DOOR_LEFT_OPEN, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, ), KEY_BINARY_PRY_THE_DOOR: BinarySensorDescription( device_key=KEY_BINARY_PRY_THE_DOOR, device_class=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, ), }, binary_entity_values={ KEY_BINARY_DOOR: BinarySensorValue( device_key=KEY_BINARY_DOOR, name="Door", native_value=False ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorValue( device_key=KEY_BINARY_DOOR_LEFT_OPEN, name="Door left open", native_value=False, ), KEY_BINARY_PRY_THE_DOOR: BinarySensorValue( device_key=KEY_BINARY_PRY_THE_DOOR, name="Pry the door", native_value=False, ), }, ) def test_Xiaomi_YLAI003(): """Test Xiaomi parser for YLAI003.""" def test_Xiaomi_YLYK01YL(): """Test Xiaomi parser for YLYK01YL.""" data_string = b"P0S\x01?tP\xe9A$\xf8\x01\x10\x03\x00\x00\x00" advertisement = bytes_to_service_info(data_string, address="F8:24:41:E9:50:74") device = XiaomiBluetoothDeviceData() assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Remote Control 5074 (YLYK01YL)", devices={ None: SensorDeviceInfo( name="Remote Control 5074", manufacturer="Xiaomi", model="YLYK01YL", hw_version=None, sw_version="Xiaomi (MiBeacon V3)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_POWER: BinarySensorDescription( device_key=KEY_POWER, device_class=BinarySensorDeviceClass.POWER, ), }, binary_entity_values={ KEY_POWER: BinarySensorValue( name="Power", device_key=KEY_POWER, native_value=True ), }, events={ DeviceKey(key="button_on", device_id=None): Event( device_key=DeviceKey(key="button_on", device_id=None), name="Button On", event_type="press", event_properties=None, ), }, ) def test_Xiaomi_YLYK01YL_FANCL(): """Test Xiaomi parser for YLYK01YL-FANCL.""" def test_Xiaomi_YLYK01YL_VENFAN(): """Test Xiaomi parser for YLYK01YL-VENFAN.""" def test_Xiaomi_YLYB01YL_BHFRC(): """Test Xiaomi parser for YLYB01YL-BHFRC.""" def test_Xiaomi_YLKG07YL_press(): """Test Xiaomi parser for YLKG07YL, YLKG08YL while pressing dimmer (no rotation).""" bindkey = "b853075158487ca39a5b5ea9" data_string = b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Dimmer Switch 988B (YLKG07YL/YLKG08YL)", devices={ None: SensorDeviceInfo( name="Dimmer Switch 988B", manufacturer="Xiaomi", model="YLKG07YL/YLKG08YL", hw_version=None, sw_version="Xiaomi (MiBeacon V3 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_DIMMER: Event( device_key=KEY_EVENT_DIMMER, name="Dimmer", event_type="press", event_properties={"number_of_presses": 1}, ), }, ) def test_Xiaomi_YLKG07YL_rotate(): """Test Xiaomi parser for YLKG07YL, YLKG08YL while rotating dimmer.""" data_string = b"X0\xb6\x036\x8b\x98\xc5A$\xf8\x8b\xb8\xf2f" b"\x13Q\x00\x00\x00\xd6" advertisement = bytes_to_service_info(data_string, address="F8:24:41:C5:98:8B") bindkey = "b853075158487ca39a5b5ea9" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Dimmer Switch 988B (YLKG07YL/YLKG08YL)", devices={ None: SensorDeviceInfo( name="Dimmer Switch 988B", manufacturer="Xiaomi", model="YLKG07YL/YLKG08YL", hw_version=None, sw_version="Xiaomi (MiBeacon V3 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_DIMMER: Event( device_key=KEY_EVENT_DIMMER, name="Dimmer", event_type="rotate_left", event_properties={"steps": 1}, ), }, ) def test_Xiaomi_K9B(): """Test Xiaomi parser for K9B.""" def test_Xiaomi_HS1BB_MI_obj4803(): """Test Xiaomi parser for Linptech HS1BB(MI) battery (4803).""" data_string = b"XY\xeb*\x9e\xe9\x8e\x058\xc1\xa4\xd0z\xd3\xe38\x00\x003c]\x10" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_BATTERY: SensorDescription( device_key=KEY_BATTERY, device_class=DeviceClass.BATTERY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_BATTERY: SensorValue( name="Battery", device_key=KEY_BATTERY, native_value=100 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_HS1BB_MI_obj4818(): """Test Xiaomi parser for Linptech HS1BB(MI) no motion time (4818).""" data_string = b"XY\xeb*\xc1\xe9\x8e\x058\xc1\xa4\x07YS\x0f\x8d8\x00\x00\xb7zp\xf8" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=False ), }, ) def test_Xiaomi_MS1BB_MI_obj4a08(): """Test Xiaomi parser for Linptech HS1BB(MI) motion + illuminance (4a08).""" data_string = b"HY\xeb*\xc2\xfc\xe0,\xa0\xb4:\xf28\x00\x00\xa2\xd9\xf0_" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement=Units.LIGHT_LUX, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=228 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=True ), }, ) def test_Xiaomi_MS1BB_MI_obj4804(): """Test Xiaomi parser for Linptech MS1BB(MI) with obj4804.""" data_string = b"XY\x89\x18\x9ag\xe5f8\xc1\xa4\x9d\xd9z\xf3&\x00\x00\xc8\xa6\x0b\xd5" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:66:E5:67") bindkey = "0fdcc30fe9289254876b5ef7c11ef1f0" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door/Window Sensor E567 (MS1BB(MI))", devices={ None: SensorDeviceInfo( name="Door/Window Sensor E567", manufacturer="Xiaomi", model="MS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_OPENING: BinarySensorDescription( device_key=KEY_BINARY_OPENING, device_class=BinarySensorDeviceClass.OPENING, ), }, binary_entity_values={ KEY_BINARY_OPENING: BinarySensorValue( device_key=KEY_BINARY_OPENING, name="Opening", native_value=True ), }, ) def test_Xiaomi_MS1BB_MI_obj4a12(): """Test Xiaomi parser for Linptech MS1BB(MI) with obj4a12.""" data_string = b"XY\x89\x18ug\xe5f8\xc1\xa4i\xdd\xf3\xa1&\x00\x00\xa2J\x1bE" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:66:E5:67") bindkey = "0fdcc30fe9289254876b5ef7c11ef1f0" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door/Window Sensor E567 (MS1BB(MI))", devices={ None: SensorDeviceInfo( name="Door/Window Sensor E567", manufacturer="Xiaomi", model="MS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_OPENING: BinarySensorDescription( device_key=KEY_BINARY_OPENING, device_class=BinarySensorDeviceClass.OPENING, ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorDescription( device_key=KEY_BINARY_DOOR_LEFT_OPEN, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, ), KEY_BINARY_DEVICE_FORCIBLY_REMOVED: BinarySensorDescription( device_key=KEY_BINARY_DEVICE_FORCIBLY_REMOVED, device_class=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, ), }, binary_entity_values={ KEY_BINARY_OPENING: BinarySensorValue( device_key=KEY_BINARY_OPENING, name="Opening", native_value=False ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorValue( device_key=KEY_BINARY_DOOR_LEFT_OPEN, name="Door left open", native_value=False, ), KEY_BINARY_DEVICE_FORCIBLY_REMOVED: BinarySensorValue( device_key=KEY_BINARY_DEVICE_FORCIBLY_REMOVED, name="Device forcibly removed", native_value=False, ), }, ) def test_Xiaomi_MS1BB_MI_obj4a13(): """Test Xiaomi parser for Linptech MS1BB(MI) with obj4a13.""" data_string = b"XY\x89\x18\x91g\xe5f8\xc1\xa4\xd6\x12\rm&\x00\x00o\xbc\x0c\xb4" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:66:E5:67") bindkey = "0fdcc30fe9289254876b5ef7c11ef1f0" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door/Window Sensor E567 (MS1BB(MI))", devices={ None: SensorDeviceInfo( name="Door/Window Sensor E567", manufacturer="Xiaomi", model="MS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_BUTTON: Event( device_key=KEY_EVENT_BUTTON, name="Button", event_type="press", event_properties=None, ), }, ) def test_Xiaomi_RS1BB_MI_obj4806(): """Test Xiaomi parser for Linptech RS1BB(MI) with obj4806.""" data_string = b"XY\x0f?JgL\xb98\xc1\xa4\xd6\xe5{\x83\x04\x00\x00\xd0\x1e\x0bK" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:B9:4C:67") bindkey = "33ede53321bc73c790a8daae4581f3d5" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Flood and Rain Sensor 4C67 (RS1BB(MI))", devices={ None: SensorDeviceInfo( name="Flood and Rain Sensor 4C67", manufacturer="Xiaomi", model="RS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_MOISTURE: BinarySensorDescription( device_key=KEY_MOISTURE, device_class=BinarySensorDeviceClass.MOISTURE, ), }, binary_entity_values={ KEY_MOISTURE: BinarySensorValue( name="Moisture", device_key=KEY_MOISTURE, native_value=False ), }, ) def test_Xiaomi_XMWXKG01YL(): """Test Xiaomi parser for XMWXKG01YL Switch (double button).""" data_string = b"XYI\x19Os\x12\x87\x83\xed\xdc\x0b48\n\x02\x00\x00\x8dI\xae(" advertisement = bytes_to_service_info(data_string, address="DC:ED:83:87:12:73") bindkey = "b93eb3787eabda352edd94b667f5d5a9" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Switch (double button) 1273 (XMWXKG01YL)", devices={ None: SensorDeviceInfo( name="Switch (double button) 1273", manufacturer="Xiaomi", model="XMWXKG01YL", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ DeviceKey(key="button_right", device_id=None): Event( device_key=DeviceKey(key="button_right", device_id=None), name="Button Right", event_type="press", event_properties=None, ), }, ) def test_Xiaomi_XMZNMS08LM_door(): """Test Xiaomi parser for XMZNMS08LM.""" bindkey = "2c3795afa33019a8afdc17ba99e6f217" data_string = b"HU9\x0e3\x9cq\xc0$\x1f\xff\xee\x80S\x00\x00\x02\xb4\xc59" advertisement = bytes_to_service_info(data_string, address="EE:89:73:44:BE:98") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock BE98 (XMZNMS08LM)", devices={ None: SensorDeviceInfo( name="Door Lock BE98", manufacturer="Xiaomi", model="XMZNMS08LM", sw_version="Xiaomi (MiBeacon V5 encrypted)", hw_version=None, ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_DOOR: BinarySensorDescription( device_key=KEY_BINARY_DOOR, device_class=BinarySensorDeviceClass.DOOR, ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorDescription( device_key=KEY_BINARY_DOOR_LEFT_OPEN, device_class=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, ), KEY_BINARY_PRY_THE_DOOR: BinarySensorDescription( device_key=KEY_BINARY_PRY_THE_DOOR, device_class=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, ), }, binary_entity_values={ KEY_BINARY_DOOR: BinarySensorValue( device_key=KEY_BINARY_DOOR, name="Door", native_value=False ), KEY_BINARY_DOOR_LEFT_OPEN: BinarySensorValue( device_key=KEY_BINARY_DOOR_LEFT_OPEN, name="Door left open", native_value=False, ), KEY_BINARY_PRY_THE_DOOR: BinarySensorValue( device_key=KEY_BINARY_PRY_THE_DOOR, name="Pry the door", native_value=False, ), }, ) def test_Xiaomi_XMZNMS08LM_lock(): """Test Xiaomi parser for XMZNMS08LM.""" bindkey = "2c3795afa33019a8afdc17ba99e6f217" data_string = ( b"\x48\x55\x39\x0e\x2f\xdf\x9d\x3f\xdd\x9a\x66\x37" b"\x13\x15\x29\xf8\x7b\x53\x00\x00\xbc\xc3\x40\x21" ) advertisement = bytes_to_service_info(data_string, address="EE:89:73:44:BE:98") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Door Lock BE98 (XMZNMS08LM)", devices={ None: SensorDeviceInfo( name="Door Lock BE98", manufacturer="Xiaomi", model="XMZNMS08LM", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), KEY_LOCK_METHOD: SensorDescription( device_key=KEY_LOCK_METHOD, device_class=ExtendedSensorDeviceClass.LOCK_METHOD, ), KEY_KEY_ID: SensorDescription( device_key=KEY_KEY_ID, device_class=ExtendedSensorDeviceClass.KEY_ID, ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), KEY_LOCK_METHOD: SensorValue( name="Lock method", device_key=KEY_LOCK_METHOD, native_value="manual" ), KEY_KEY_ID: SensorValue( name="Key id", device_key=KEY_KEY_ID, native_value="administrator" ), }, binary_entity_descriptions={ KEY_BINARY_LOCK: BinarySensorDescription( device_key=KEY_BINARY_LOCK, device_class=BinarySensorDeviceClass.LOCK, ), }, binary_entity_values={ KEY_BINARY_LOCK: BinarySensorValue( device_key=KEY_BINARY_LOCK, name="Lock", native_value=True ), }, events={ DeviceKey(key="lock", device_id=None): Event( device_key=DeviceKey(key="lock", device_id=None), name="Lock", event_type="unlock_inside_the_door", event_properties=None, ), }, ) def test_Xiaomi_HS1BB_battery(): """Test Xiaomi parser for HS1BB battery.""" data_string = b"XY\xeb*\x9e\xe9\x8e\x058\xc1\xa4\xd0z\xd3\xe38\x00\x003c]\x10" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_BATTERY: SensorDescription( device_key=KEY_BATTERY, device_class=DeviceClass.BATTERY, native_unit_of_measurement="%", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_BATTERY: SensorValue( name="Battery", device_key=KEY_BATTERY, native_value=100 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, ) def test_Xiaomi_HS1BB_no_motion(): """Test Xiaomi parser for HS1BB.""" data_string = b"XY\xeb*\xc1\xe9\x8e\x058\xc1\xa4\x07YS\x0f\x8d8\x00\x00\xb7zp\xf8" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=False ), }, ) def test_Xiaomi_HS1BB_illuminanca_and_motion(): """Test Xiaomi parser for HS1BB illuminance and motion.""" data_string = b"HY\xeb*\xc2\xfc\xe0,\xa0\xb4:\xf28\x00\x00\xa2\xd9\xf0_" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:05:8E:E9") bindkey = "7475a4a77584401780ffc3ee62dd353c" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8EE9 (HS1BB(MI))", devices={ None: SensorDeviceInfo( name="Motion Sensor 8EE9", manufacturer="Xiaomi", model="HS1BB(MI)", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement="lx", ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=228 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=True ), }, ) def test_Xiaomi_DSL_C08(): """Test Xiaomi parser for DSL-C08.""" def test_Xiaomi_RTCGQ02LM_light_and_motion(): """Test Xiaomi parser for RTCGQ02LM.""" data_string = b"XY\x8d\n\x8cw\x8e <\xc2\x18Z'6(\xec2\x06\x00\x00\xc4&@\x15" advertisement = bytes_to_service_info(data_string, address="18:C2:3C:20:8E:77") bindkey = "4960bb9f8711b4ffd7df1756d11427ae" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.sleepy_device assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8E77 (RTCGQ02LM)", devices={ None: SensorDeviceInfo( name="Motion Sensor 8E77", manufacturer="Xiaomi", model="RTCGQ02LM", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), KEY_BINARY_LIGHT: BinarySensorDescription( device_key=KEY_BINARY_LIGHT, device_class=BinarySensorDeviceClass.LIGHT, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=True ), KEY_BINARY_LIGHT: BinarySensorValue( device_key=KEY_BINARY_LIGHT, name="Light", native_value=True ), }, ) def test_Xiaomi_RTCGQ02LM_timeout_motion(): """Test Xiaomi parser for RTCGQ02LM.""" data_string = b"HY\x8d\n\x92ySu\x0f\xed\x0f\x99\x06\x00\x00\\\xad,)" advertisement = bytes_to_service_info(data_string, address="18:C2:3C:20:8E:77") bindkey = "4960bb9f8711b4ffd7df1756d11427ae" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor 8E77 (RTCGQ02LM)", devices={ None: SensorDeviceInfo( name="Motion Sensor 8E77", manufacturer="Xiaomi", model="RTCGQ02LM", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=False ), }, ) def test_Xiaomi_XMPIRO2SXS(): """Test Xiaomi parser for Xiaomi Human Body Sensor 2S XMPIRO2SXS.""" data_string = b"HY15\x0bdy\x91\x173\x1e\xf4\x02\x00\x00\xc5\xd2\xf6\xac" advertisement = bytes_to_service_info(data_string, address="DC:8E:95:2D:EA:43") bindkey = "685d647dc5e7bc9bcfcf5a1357bd2114" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Motion Sensor EA43 (XMPIRO2SXS)", devices={ None: SensorDeviceInfo( name="Motion Sensor EA43", manufacturer="Xiaomi", model="XMPIRO2SXS", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement=Units.LIGHT_LUX, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=51 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_MOTION: BinarySensorDescription( device_key=KEY_BINARY_MOTION, device_class=BinarySensorDeviceClass.MOTION, ), }, binary_entity_values={ KEY_BINARY_MOTION: BinarySensorValue( device_key=KEY_BINARY_MOTION, name="Motion", native_value=True ), }, ) def test_Xiaomi_XMOSB01XS_ILLUMINANCE(): """Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS.""" data_string = ( b"\x48\x59\x83\x46\x0d\xdc\x21\x3c\xe9\x81\xda\x7a\xe2\x02\x00\x44\x41\xf8\x8c" ) advertisement = bytes_to_service_info(data_string, address="0C:43:14:A1:41:1E") bindkey = "0a4552cb19a639b72b8ed09bde6d5bfa" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.sleepy_device assert device.update(advertisement) == SensorUpdate( title="Occupancy Sensor 411E (XMOSB01XS)", devices={ None: SensorDeviceInfo( name="Occupancy Sensor 411E", manufacturer="Xiaomi", model="XMOSB01XS", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_ILLUMINANCE: SensorDescription( device_key=KEY_ILLUMINANCE, device_class=DeviceClass.ILLUMINANCE, native_unit_of_measurement=Units.LIGHT_LUX, ), KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_ILLUMINANCE: SensorValue( name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=38 ), KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={}, binary_entity_values={}, ) def test_Xiaomi_XMOSB01XS_OCCUPANCY(): """Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS.""" data_string = ( b"\x58\x59\x83\x46\x1f\xbd\xb1\xc4\x67\x48\xd4" b"\x9d\x1e\xfd\x8c\x04\x00\x00\xe5\x7e\x87\x3a" ) advertisement = bytes_to_service_info(data_string, address="D4:48:67:C4:B1:BD") bindkey = "920ce119b34410d38251ccea54c0f915" device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.sleepy_device assert device.update(advertisement) == SensorUpdate( title="Occupancy Sensor B1BD (XMOSB01XS)", devices={ None: SensorDeviceInfo( name="Occupancy Sensor B1BD", manufacturer="Xiaomi", model="XMOSB01XS", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, binary_entity_descriptions={ KEY_BINARY_OCCUPANCY: BinarySensorDescription( device_key=KEY_BINARY_OCCUPANCY, device_class=BinarySensorDeviceClass.OCCUPANCY, ), }, binary_entity_values={ KEY_BINARY_OCCUPANCY: BinarySensorValue( device_key=KEY_BINARY_OCCUPANCY, name="Occupancy", native_value=False ), }, ) def test_Xiaomi_PTX_press(): """Test Xiaomi parser for Xiaomi PTX YK1 QMIMB.""" bindkey = "a74510b40386d35ae6227a7451efc76e" data_string = b"XY\xbb8\x04\xad\xb9\xa58\xc1\xa4\xdc\x10\xb5\x04\x00\x00,\x12/\xb6" advertisement = bytes_to_service_info(data_string, address="A4:C1:38:A5:B9:AD") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Wireless Switch B9AD (PTX_YK1_QMIMB)", devices={ None: SensorDeviceInfo( name="Wireless Switch B9AD", manufacturer="Xiaomi", model="PTX_YK1_QMIMB", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_BUTTON: Event( device_key=KEY_EVENT_BUTTON, name="Button", event_type="press", event_properties=None, ), }, ) def test_Xiaomi_XMWXKG01LM_press(): """Test Xiaomi parser for Xiaomi XMWXKG01LM .""" bindkey = "7202a2d4201bbf82ea5bb3705657c32a" data_string = b"XY\x87#5\x057$<\xc2\x18\xd6w\x94\x02\x00\x00\xcb-\xe3\t" advertisement = bytes_to_service_info(data_string, address="18:C2:3C:24:37:05") device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( title="Button 3705 (XMWXKG01LM)", devices={ None: SensorDeviceInfo( name="Button 3705", manufacturer="Xiaomi", model="XMWXKG01LM", hw_version=None, sw_version="Xiaomi (MiBeacon V5 encrypted)", ) }, entity_descriptions={ KEY_SIGNAL_STRENGTH: SensorDescription( device_key=KEY_SIGNAL_STRENGTH, device_class=DeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement="dBm", ), }, entity_values={ KEY_SIGNAL_STRENGTH: SensorValue( name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 ), }, events={ KEY_EVENT_BUTTON: Event( device_key=KEY_EVENT_BUTTON, name="Button", event_type="press", event_properties=None, ), }, ) def test_can_create(): XiaomiBluetoothDeviceData() Bluetooth-Devices-xiaomi-ble-b47183c/tests/__init__.py0000664000175000017500000000000014772435047025030 0ustar billchenchinabillchenchinaBluetooth-Devices-xiaomi-ble-b47183c/extract_tokens.py0000664000175000017500000000430514772435047025200 0ustar billchenchinabillchenchinaimport asyncio import pprint from getpass import getpass from aiohttp import ClientSession from xiaomi_ble import XiaomiCloudTokenFetch from xiaomi_ble.cloud import SERVERS # Adapted from PiotrMachowski's Xiaomi-cloud-tokens-extractor # MIT License # # Copyright (c) 2020 Piotr Machowski # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. async def main(username: str, password: str, mac: str, servers: list[str]) -> None: async with ClientSession() as session: fetcher = XiaomiCloudTokenFetch(username, password, session) device_info = await fetcher.get_device_info(mac, servers) if device_info: pprint.pprint(device_info) return print(f"No devices found matching the provided MAC address: {mac}.") print("Username (email or user ID):") username = input() print("Password:") password = getpass("") print("Mac address:") mac = input() print(f"Server (one of: {','.join(SERVERS)}) Leave empty to check all available:") server = input() while server not in ["", *SERVERS]: print(f"Invalid server provided. Valid values: {','.join(SERVERS)}") print("Server:") server = input() print() asyncio.run(main(username, password, mac, [server] if server else SERVERS))