PySwitchbot 2.4.0__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/PKG-INFO +1 -1
  2. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/pyproject.toml +1 -1
  3. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/__init__.py +2 -0
  4. pyswitchbot-2.5.0/switchbot/__version__.py +1 -0
  5. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parser.py +13 -1
  6. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/climate_panel.py +18 -0
  7. pyswitchbot-2.5.0/switchbot/adv_parsers/remote.py +42 -0
  8. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/__init__.py +1 -0
  9. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/fan.py +6 -0
  10. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/device.py +1 -0
  11. pyswitchbot-2.5.0/switchbot/devices/universal_remote.py +23 -0
  12. pyswitchbot-2.4.0/switchbot/__version__.py +0 -1
  13. pyswitchbot-2.4.0/switchbot/adv_parsers/remote.py +0 -23
  14. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/LICENSE +0 -0
  15. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/README.md +0 -0
  16. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/__init__.py +0 -0
  17. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/_sensor_th.py +0 -0
  18. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/air_purifier.py +0 -0
  19. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/art_frame.py +0 -0
  20. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
  21. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/bot.py +0 -0
  22. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/bulb.py +0 -0
  23. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  24. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/contact.py +0 -0
  25. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/curtain.py +0 -0
  26. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/fan.py +0 -0
  27. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/hub2.py +0 -0
  28. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/hub3.py +0 -0
  29. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/hubmini_matter.py +0 -0
  30. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/humidifier.py +0 -0
  31. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/keypad.py +0 -0
  32. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/keypad_vision.py +0 -0
  33. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/leak.py +0 -0
  34. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/light_strip.py +0 -0
  35. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/lock.py +0 -0
  36. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/meter.py +0 -0
  37. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/motion.py +0 -0
  38. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/plug.py +0 -0
  39. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/presence_sensor.py +0 -0
  40. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/relay_switch.py +0 -0
  41. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/roller_shade.py +0 -0
  42. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/smart_thermostat_radiator.py +0 -0
  43. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/vacuum.py +0 -0
  44. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/adv_parsers/weather_station.py +0 -0
  45. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/api_config.py +0 -0
  46. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/air_purifier.py +0 -0
  47. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/climate.py +0 -0
  48. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/evaporative_humidifier.py +0 -0
  49. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/hub2.py +0 -0
  50. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/hub3.py +0 -0
  51. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/light.py +0 -0
  52. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/lock.py +0 -0
  53. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/const/presence_sensor.py +0 -0
  54. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/__init__.py +0 -0
  55. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/air_purifier.py +0 -0
  56. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/art_frame.py +0 -0
  57. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/base_cover.py +0 -0
  58. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/base_light.py +0 -0
  59. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/blind_tilt.py +0 -0
  60. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/bot.py +0 -0
  61. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/bulb.py +0 -0
  62. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/ceiling_light.py +0 -0
  63. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/contact.py +0 -0
  64. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/curtain.py +0 -0
  65. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/evaporative_humidifier.py +0 -0
  66. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/fan.py +0 -0
  67. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/humidifier.py +0 -0
  68. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/keypad.py +0 -0
  69. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/keypad_vision.py +0 -0
  70. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/light_strip.py +0 -0
  71. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/lock.py +0 -0
  72. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/meter.py +0 -0
  73. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/meter_pro.py +0 -0
  74. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/motion.py +0 -0
  75. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/plug.py +0 -0
  76. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/relay_switch.py +0 -0
  77. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/roller_shade.py +0 -0
  78. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/smart_thermostat_radiator.py +0 -0
  79. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/devices/vacuum.py +0 -0
  80. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/discovery.py +0 -0
  81. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/enum.py +0 -0
  82. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/helpers.py +0 -0
  83. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/models.py +0 -0
  84. {pyswitchbot-2.4.0 → pyswitchbot-2.5.0}/switchbot/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "PySwitchbot"
3
- version = "2.4.0"
3
+ version = "2.5.0"
4
4
  description = "A library to communicate with Switchbot"
5
5
  authors = ["Daniel Hjelseth Hoyer"]
6
6
  license = "MIT"
@@ -70,6 +70,7 @@ from .devices.relay_switch import (
70
70
  )
71
71
  from .devices.roller_shade import SwitchbotRollerShade
72
72
  from .devices.smart_thermostat_radiator import SwitchbotSmartThermostatRadiator
73
+ from .devices.universal_remote import SwitchbotUniversalRemote
73
74
  from .devices.vacuum import SwitchbotVacuum
74
75
  from .discovery import GetSwitchbotDevices
75
76
  from .models import SwitchBotAdvertisement
@@ -131,6 +132,7 @@ __all__ = [
131
132
  "SwitchbotStandingFan",
132
133
  "SwitchbotStripLight3",
133
134
  "SwitchbotSupportedType",
135
+ "SwitchbotUniversalRemote",
134
136
  "SwitchbotVacuum",
135
137
  "VerticalOscillationAngle",
136
138
  "close_stale_connections",
@@ -0,0 +1 @@
1
+ __version__ = "2.5.0"
@@ -51,7 +51,7 @@ from .adv_parsers.relay_switch import (
51
51
  process_relay_switch_2pm,
52
52
  process_relay_switch_common_data,
53
53
  )
54
- from .adv_parsers.remote import process_woremote
54
+ from .adv_parsers.remote import process_woremote, process_wouniversal_remote
55
55
  from .adv_parsers.roller_shade import process_worollershade
56
56
  from .adv_parsers.smart_thermostat_radiator import process_smart_thermostat_radiator
57
57
  from .adv_parsers.vacuum import process_vacuum, process_vacuum_k
@@ -402,6 +402,18 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
402
402
  "func": process_woremote,
403
403
  "manufacturer_id": 89,
404
404
  },
405
+ "\x07": {
406
+ "modelName": SwitchbotModel.UNIVERSAL_REMOTE,
407
+ "modelFriendlyName": "Universal Remote",
408
+ "func": process_wouniversal_remote,
409
+ "manufacturer_id": 2409,
410
+ },
411
+ "'": {
412
+ "modelName": SwitchbotModel.UNIVERSAL_REMOTE,
413
+ "modelFriendlyName": "Universal Remote",
414
+ "func": process_wouniversal_remote,
415
+ "manufacturer_id": 2409,
416
+ },
405
417
  ",": {
406
418
  "modelName": SwitchbotModel.ROLLER_SHADE,
407
419
  "modelFriendlyName": "Roller Shade",
@@ -28,6 +28,18 @@ def process_climate_panel(
28
28
  pir_state = bool(mfr_data[15] & 0x80)
29
29
  is_light = ((mfr_data[15] >> 1) & 0x03) == 0x02
30
30
 
31
+ # Keystate bytes report physical ON/OFF button presses. For each byte,
32
+ # Bit[7:5] is the press mode (0: power-on init, 1: single, 2: double,
33
+ # 3: long) and Bit[4:0] is a 1-30 cyclic counter that resets to 1 when the
34
+ # mode changes. A value of 0x00 is the power-on state and must not trigger.
35
+ # The raw byte is also exposed so consumers can watch it for any change.
36
+ on_keystate = mfr_data[13]
37
+ off_keystate = mfr_data[14]
38
+ on_keystate_mode = (on_keystate >> 5) & 0x07
39
+ on_keystate_counter = on_keystate & 0x1F
40
+ off_keystate_mode = (off_keystate >> 5) & 0x07
41
+ off_keystate_counter = off_keystate & 0x1F
42
+
31
43
  result = {
32
44
  "sequence_number": seq_number,
33
45
  "isOn": isOn,
@@ -38,6 +50,12 @@ def process_climate_panel(
38
50
  "humidity_alarm": humidity_alarm,
39
51
  "motion_detected": pir_state,
40
52
  "is_light": is_light,
53
+ "on_keystate": on_keystate,
54
+ "off_keystate": off_keystate,
55
+ "on_keystate_mode": on_keystate_mode,
56
+ "on_keystate_counter": on_keystate_counter,
57
+ "off_keystate_mode": off_keystate_mode,
58
+ "off_keystate_counter": off_keystate_counter,
41
59
  }
42
60
 
43
61
  _LOGGER.debug(
@@ -0,0 +1,42 @@
1
+ """Remote adv parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ _LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ def process_woremote(
11
+ data: bytes | None, mfr_data: bytes | None
12
+ ) -> dict[str, int | None]:
13
+ """Process WoRemote adv data."""
14
+ if data is None or len(data) < 3:
15
+ return {
16
+ "battery": None,
17
+ }
18
+
19
+ _LOGGER.debug("data: %s", data.hex())
20
+
21
+ return {
22
+ "battery": data[2] & 0b01111111,
23
+ }
24
+
25
+
26
+ def process_wouniversal_remote(
27
+ data: bytes | None, mfr_data: bytes | None
28
+ ) -> dict[str, bool | int | None]:
29
+ """Process Universal Remote adv data."""
30
+ if mfr_data is None or len(mfr_data) < 8:
31
+ return {
32
+ "battery": None,
33
+ "charging": None,
34
+ }
35
+
36
+ _LOGGER.debug("mfr_data: %s", mfr_data.hex())
37
+
38
+ # mfr_data[7]: bit 7 is charging; bits 6-0 are battery percentage.
39
+ return {
40
+ "battery": mfr_data[7] & 0b01111111,
41
+ "charging": bool((mfr_data[7] >> 7) & 1),
42
+ }
@@ -82,6 +82,7 @@ class SwitchbotModel(StrEnum):
82
82
  RELAY_SWITCH_1PM = "Relay Switch 1PM"
83
83
  RELAY_SWITCH_1 = "Relay Switch 1"
84
84
  REMOTE = "WoRemote"
85
+ UNIVERSAL_REMOTE = "WoUniversalRemote"
85
86
  EVAPORATIVE_HUMIDIFIER = "Evaporative Humidifier"
86
87
  ROLLER_SHADE = "Roller Shade"
87
88
  HUBMINI_MATTER = "HubMini Matter"
@@ -60,3 +60,9 @@ class VerticalOscillationAngle(Enum):
60
60
  ANGLE_30 = 30
61
61
  ANGLE_60 = 60
62
62
  ANGLE_90 = 95
63
+
64
+ @classmethod
65
+ def _missing_(cls, value: int) -> VerticalOscillationAngle | None:
66
+ if value == 90:
67
+ return cls.ANGLE_90
68
+ return None
@@ -74,6 +74,7 @@ API_MODEL_TO_ENUM: dict[str, SwitchbotModel] = {
74
74
  "WoBlindTilt": SwitchbotModel.BLIND_TILT,
75
75
  "WoIOSensor": SwitchbotModel.IO_METER, # Outdoor Meter
76
76
  "WoButton": SwitchbotModel.REMOTE, # Remote button
77
+ "WoUniversalRemote": SwitchbotModel.UNIVERSAL_REMOTE, # Universal Remote
77
78
  "WoLinkMini": SwitchbotModel.HUBMINI_MATTER, # Hub Mini
78
79
  "WoFan2": SwitchbotModel.CIRCULATOR_FAN,
79
80
  "WoHub2": SwitchbotModel.HUB2,
@@ -0,0 +1,23 @@
1
+ """Library to handle connection with Switchbot Universal Remote."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from .device import SwitchbotDevice
9
+
10
+ _LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ class SwitchbotUniversalRemote(SwitchbotDevice):
14
+ """Representation of a Switchbot Universal Remote."""
15
+
16
+ async def get_basic_info(self) -> dict[str, Any] | None:
17
+ """Get device basic settings."""
18
+ if not (_data := await self._get_basic_info()):
19
+ return None
20
+ return {
21
+ "battery": _data[1],
22
+ "charging": bool(_data[12]),
23
+ }
@@ -1 +0,0 @@
1
- __version__ = "2.4.0"
@@ -1,23 +0,0 @@
1
- """Remote adv parser."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
-
7
- _LOGGER = logging.getLogger(__name__)
8
-
9
-
10
- def process_woremote(
11
- data: bytes | None, mfr_data: bytes | None
12
- ) -> dict[str, int | None]:
13
- """Process WoRemote adv data."""
14
- if data is None or len(data) < 3:
15
- return {
16
- "battery": None,
17
- }
18
-
19
- _LOGGER.debug("data: %s", data.hex())
20
-
21
- return {
22
- "battery": data[2] & 0b01111111,
23
- }
File without changes
File without changes