PySwitchbot 2.1.0__tar.gz → 2.3.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.
- {pyswitchbot-2.1.0/PySwitchbot.egg-info → pyswitchbot-2.3.0}/PKG-INFO +46 -22
- pyswitchbot-2.1.0/PKG-INFO → pyswitchbot-2.3.0/README.md +27 -35
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/pyproject.toml +73 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/__init__.py +18 -1
- pyswitchbot-2.3.0/switchbot/__version__.py +1 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parser.py +94 -3
- pyswitchbot-2.3.0/switchbot/adv_parsers/_sensor_th.py +36 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/air_purifier.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/art_frame.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/blind_tilt.py +2 -2
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/bot.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/bulb.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/ceiling_light.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/climate_panel.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/contact.py +8 -3
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/curtain.py +3 -3
- pyswitchbot-2.3.0/switchbot/adv_parsers/fan.py +62 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/hub2.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/hub3.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/hubmini_matter.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/humidifier.py +2 -2
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/keypad.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/keypad_vision.py +3 -3
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/leak.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/light_strip.py +30 -2
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/lock.py +4 -4
- pyswitchbot-2.3.0/switchbot/adv_parsers/meter.py +44 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/motion.py +2 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/plug.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/presence_sensor.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/remote.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/roller_shade.py +2 -2
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/smart_thermostat_radiator.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/vacuum.py +2 -2
- pyswitchbot-2.3.0/switchbot/adv_parsers/weather_station.py +40 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/__init__.py +18 -1
- pyswitchbot-2.3.0/switchbot/const/fan.py +62 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/light.py +12 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/air_purifier.py +1 -26
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/art_frame.py +2 -26
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/device.py +20 -5
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/evaporative_humidifier.py +2 -27
- pyswitchbot-2.3.0/switchbot/devices/fan.py +307 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/keypad_vision.py +1 -1
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/light_strip.py +196 -51
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/lock.py +7 -16
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/relay_switch.py +28 -48
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/roller_shade.py +55 -17
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/smart_thermostat_radiator.py +13 -26
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/discovery.py +2 -4
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/utils.py +1 -1
- pyswitchbot-2.1.0/MANIFEST.in +0 -1
- pyswitchbot-2.1.0/PySwitchbot.egg-info/SOURCES.txt +0 -110
- pyswitchbot-2.1.0/PySwitchbot.egg-info/dependency_links.txt +0 -1
- pyswitchbot-2.1.0/PySwitchbot.egg-info/requires.txt +0 -5
- pyswitchbot-2.1.0/PySwitchbot.egg-info/top_level.txt +0 -1
- pyswitchbot-2.1.0/README.md +0 -110
- pyswitchbot-2.1.0/setup.cfg +0 -4
- pyswitchbot-2.1.0/setup.py +0 -40
- pyswitchbot-2.1.0/switchbot/adv_parsers/fan.py +0 -33
- pyswitchbot-2.1.0/switchbot/adv_parsers/meter.py +0 -56
- pyswitchbot-2.1.0/switchbot/const/fan.py +0 -14
- pyswitchbot-2.1.0/switchbot/devices/fan.py +0 -103
- pyswitchbot-2.1.0/tests/test_adv_parser.py +0 -4444
- pyswitchbot-2.1.0/tests/test_air_purifier.py +0 -484
- pyswitchbot-2.1.0/tests/test_art_frame.py +0 -221
- pyswitchbot-2.1.0/tests/test_base_cover.py +0 -151
- pyswitchbot-2.1.0/tests/test_blind_tilt.py +0 -240
- pyswitchbot-2.1.0/tests/test_bulb.py +0 -241
- pyswitchbot-2.1.0/tests/test_ceiling_light.py +0 -194
- pyswitchbot-2.1.0/tests/test_colormode_imports.py +0 -88
- pyswitchbot-2.1.0/tests/test_curtain.py +0 -425
- pyswitchbot-2.1.0/tests/test_device.py +0 -416
- pyswitchbot-2.1.0/tests/test_discovery_callback.py +0 -142
- pyswitchbot-2.1.0/tests/test_encrypted_device.py +0 -548
- pyswitchbot-2.1.0/tests/test_evaporative_humidifier.py +0 -358
- pyswitchbot-2.1.0/tests/test_fan.py +0 -177
- pyswitchbot-2.1.0/tests/test_helpers.py +0 -72
- pyswitchbot-2.1.0/tests/test_hub2.py +0 -18
- pyswitchbot-2.1.0/tests/test_hub3.py +0 -13
- pyswitchbot-2.1.0/tests/test_keypad_vision.py +0 -259
- pyswitchbot-2.1.0/tests/test_lock.py +0 -881
- pyswitchbot-2.1.0/tests/test_meter_pro.py +0 -249
- pyswitchbot-2.1.0/tests/test_relay_switch.py +0 -503
- pyswitchbot-2.1.0/tests/test_roller_shade.py +0 -223
- pyswitchbot-2.1.0/tests/test_smart_thermostat_radiator.py +0 -239
- pyswitchbot-2.1.0/tests/test_strip_light.py +0 -398
- pyswitchbot-2.1.0/tests/test_utils.py +0 -41
- pyswitchbot-2.1.0/tests/test_vacuum.py +0 -145
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/LICENSE +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/adv_parsers/relay_switch.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/air_purifier.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/climate.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/evaporative_humidifier.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/hub2.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/hub3.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/lock.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/const/presence_sensor.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/base_cover.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/meter_pro.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/devices/vacuum.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/enum.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/helpers.py +0 -0
- {pyswitchbot-2.1.0 → pyswitchbot-2.3.0}/switchbot/models.py +0 -0
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySwitchbot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A library to communicate with Switchbot
|
|
5
|
-
Home-page: https://github.com/sblibs/pySwitchbot/
|
|
6
|
-
Author: Daniel Hjelseth Hoyer
|
|
7
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Daniel Hjelseth Hoyer
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Environment :: Other Environment
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
13
|
Classifier: Operating System :: OS Independent
|
|
12
14
|
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
20
|
Classifier: Topic :: Home Automation
|
|
14
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Requires-
|
|
22
|
+
Requires-Dist: aiohttp (>=3.9.5)
|
|
23
|
+
Requires-Dist: bleak (>=0.19.0)
|
|
24
|
+
Requires-Dist: bleak-retry-connector (>=3.4.0)
|
|
25
|
+
Requires-Dist: cryptography (>=39.0.0)
|
|
26
|
+
Requires-Dist: pyOpenSSL (>=23.0.0)
|
|
27
|
+
Project-URL: Bug Tracker, https://github.com/sblibs/pySwitchbot/issues
|
|
28
|
+
Project-URL: Changelog, https://github.com/sblibs/pySwitchbot/blob/main/CHANGELOG.md
|
|
29
|
+
Project-URL: Repository, https://github.com/sblibs/pySwitchbot/
|
|
16
30
|
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: aiohttp>=3.9.5
|
|
19
|
-
Requires-Dist: bleak>=0.19.0
|
|
20
|
-
Requires-Dist: bleak-retry-connector>=3.4.0
|
|
21
|
-
Requires-Dist: cryptography>=39.0.0
|
|
22
|
-
Requires-Dist: pyOpenSSL>=23.0.0
|
|
23
|
-
Dynamic: author
|
|
24
|
-
Dynamic: classifier
|
|
25
|
-
Dynamic: description
|
|
26
|
-
Dynamic: description-content-type
|
|
27
|
-
Dynamic: home-page
|
|
28
|
-
Dynamic: license
|
|
29
|
-
Dynamic: license-file
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: requires-python
|
|
32
|
-
Dynamic: summary
|
|
33
31
|
|
|
34
32
|
# pySwitchbot [](https://codecov.io/gh/sblibs/pySwitchbot)
|
|
35
33
|
|
|
@@ -58,6 +56,31 @@ Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
58
56
|
Where `MAC` is MAC address of the lock and `USERNAME` is your SwitchBot account username, after that script will ask for your password.
|
|
59
57
|
If authentication succeeds then script should output your key id and encryption key.
|
|
60
58
|
|
|
59
|
+
### Troubleshooting key retrieval
|
|
60
|
+
|
|
61
|
+
Key retrieval talks to SwitchBot's account API with your username and
|
|
62
|
+
password. The most common failures are account-side, not bugs in this library:
|
|
63
|
+
|
|
64
|
+
- **`Authentication failed: ...`** — the username/password were rejected. This
|
|
65
|
+
happens when:
|
|
66
|
+
- Two-factor authentication (2FA) is enabled on the account. The API login
|
|
67
|
+
used here does not support a verification code, so 2FA accounts cannot
|
|
68
|
+
retrieve keys this way — temporarily disable 2FA, fetch the key, then
|
|
69
|
+
re-enable it.
|
|
70
|
+
- The account was created via "Sign in with Apple"/Google and has no
|
|
71
|
+
password set. Set a password in the SwitchBot app first, or use an
|
|
72
|
+
email/password account.
|
|
73
|
+
- The username is an email but the account is registered to a phone number
|
|
74
|
+
(or vice versa). Use the exact identifier you log in with.
|
|
75
|
+
- **`Failed to retrieve encryption key from SwitchBot Account: ...`** —
|
|
76
|
+
authentication succeeded but the key could not be read. Usually the account
|
|
77
|
+
is not the device **owner**: keys are only returned to the owning account,
|
|
78
|
+
not to shared/family members. Retrieve the key from the owner account, or
|
|
79
|
+
transfer ownership in the app.
|
|
80
|
+
|
|
81
|
+
The key only needs to be fetched once; store the `key_id` and encryption key
|
|
82
|
+
and reuse them — there is no need to call the script on every connection.
|
|
83
|
+
|
|
61
84
|
## Examples:
|
|
62
85
|
|
|
63
86
|
#### WoLock (Lock-Pro)
|
|
@@ -79,7 +102,7 @@ LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
|
79
102
|
async def main():
|
|
80
103
|
wolock = await GetSwitchbotDevices().get_locks()
|
|
81
104
|
await lock.SwitchbotLock(
|
|
82
|
-
wolock[BLE_MAC].device, KEY_ID,
|
|
105
|
+
wolock[BLE_MAC].device, KEY_ID, ENC_KEY, model=LOCK_MODEL
|
|
83
106
|
).unlock()
|
|
84
107
|
|
|
85
108
|
|
|
@@ -103,7 +126,7 @@ LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
|
103
126
|
async def main():
|
|
104
127
|
wolock = await GetSwitchbotDevices().get_locks()
|
|
105
128
|
await lock.SwitchbotLock(
|
|
106
|
-
wolock[BLE_MAC].device, KEY_ID,
|
|
129
|
+
wolock[BLE_MAC].device, KEY_ID, ENC_KEY, model=LOCK_MODEL
|
|
107
130
|
).lock()
|
|
108
131
|
|
|
109
132
|
|
|
@@ -141,3 +164,4 @@ async def main():
|
|
|
141
164
|
if __name__ == "__main__":
|
|
142
165
|
asyncio.run(main())
|
|
143
166
|
```
|
|
167
|
+
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: PySwitchbot
|
|
3
|
-
Version: 2.1.0
|
|
4
|
-
Summary: A library to communicate with Switchbot
|
|
5
|
-
Home-page: https://github.com/sblibs/pySwitchbot/
|
|
6
|
-
Author: Daniel Hjelseth Hoyer
|
|
7
|
-
License: MIT
|
|
8
|
-
Classifier: Development Status :: 3 - Alpha
|
|
9
|
-
Classifier: Environment :: Other Environment
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Classifier: Programming Language :: Python
|
|
13
|
-
Classifier: Topic :: Home Automation
|
|
14
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Requires-Python: >=3.11
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
Requires-Dist: aiohttp>=3.9.5
|
|
19
|
-
Requires-Dist: bleak>=0.19.0
|
|
20
|
-
Requires-Dist: bleak-retry-connector>=3.4.0
|
|
21
|
-
Requires-Dist: cryptography>=39.0.0
|
|
22
|
-
Requires-Dist: pyOpenSSL>=23.0.0
|
|
23
|
-
Dynamic: author
|
|
24
|
-
Dynamic: classifier
|
|
25
|
-
Dynamic: description
|
|
26
|
-
Dynamic: description-content-type
|
|
27
|
-
Dynamic: home-page
|
|
28
|
-
Dynamic: license
|
|
29
|
-
Dynamic: license-file
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: requires-python
|
|
32
|
-
Dynamic: summary
|
|
33
|
-
|
|
34
1
|
# pySwitchbot [](https://codecov.io/gh/sblibs/pySwitchbot)
|
|
35
2
|
|
|
36
3
|
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
@@ -58,6 +25,31 @@ Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
58
25
|
Where `MAC` is MAC address of the lock and `USERNAME` is your SwitchBot account username, after that script will ask for your password.
|
|
59
26
|
If authentication succeeds then script should output your key id and encryption key.
|
|
60
27
|
|
|
28
|
+
### Troubleshooting key retrieval
|
|
29
|
+
|
|
30
|
+
Key retrieval talks to SwitchBot's account API with your username and
|
|
31
|
+
password. The most common failures are account-side, not bugs in this library:
|
|
32
|
+
|
|
33
|
+
- **`Authentication failed: ...`** — the username/password were rejected. This
|
|
34
|
+
happens when:
|
|
35
|
+
- Two-factor authentication (2FA) is enabled on the account. The API login
|
|
36
|
+
used here does not support a verification code, so 2FA accounts cannot
|
|
37
|
+
retrieve keys this way — temporarily disable 2FA, fetch the key, then
|
|
38
|
+
re-enable it.
|
|
39
|
+
- The account was created via "Sign in with Apple"/Google and has no
|
|
40
|
+
password set. Set a password in the SwitchBot app first, or use an
|
|
41
|
+
email/password account.
|
|
42
|
+
- The username is an email but the account is registered to a phone number
|
|
43
|
+
(or vice versa). Use the exact identifier you log in with.
|
|
44
|
+
- **`Failed to retrieve encryption key from SwitchBot Account: ...`** —
|
|
45
|
+
authentication succeeded but the key could not be read. Usually the account
|
|
46
|
+
is not the device **owner**: keys are only returned to the owning account,
|
|
47
|
+
not to shared/family members. Retrieve the key from the owner account, or
|
|
48
|
+
transfer ownership in the app.
|
|
49
|
+
|
|
50
|
+
The key only needs to be fetched once; store the `key_id` and encryption key
|
|
51
|
+
and reuse them — there is no need to call the script on every connection.
|
|
52
|
+
|
|
61
53
|
## Examples:
|
|
62
54
|
|
|
63
55
|
#### WoLock (Lock-Pro)
|
|
@@ -79,7 +71,7 @@ LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
|
79
71
|
async def main():
|
|
80
72
|
wolock = await GetSwitchbotDevices().get_locks()
|
|
81
73
|
await lock.SwitchbotLock(
|
|
82
|
-
wolock[BLE_MAC].device, KEY_ID,
|
|
74
|
+
wolock[BLE_MAC].device, KEY_ID, ENC_KEY, model=LOCK_MODEL
|
|
83
75
|
).unlock()
|
|
84
76
|
|
|
85
77
|
|
|
@@ -103,7 +95,7 @@ LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
|
103
95
|
async def main():
|
|
104
96
|
wolock = await GetSwitchbotDevices().get_locks()
|
|
105
97
|
await lock.SwitchbotLock(
|
|
106
|
-
wolock[BLE_MAC].device, KEY_ID,
|
|
98
|
+
wolock[BLE_MAC].device, KEY_ID, ENC_KEY, model=LOCK_MODEL
|
|
107
99
|
).lock()
|
|
108
100
|
|
|
109
101
|
|
|
@@ -1,8 +1,64 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "PySwitchbot"
|
|
3
|
+
version = "2.3.0"
|
|
4
|
+
description = "A library to communicate with Switchbot"
|
|
5
|
+
authors = ["Daniel Hjelseth Hoyer"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
repository = "https://github.com/sblibs/pySwitchbot/"
|
|
9
|
+
packages = [
|
|
10
|
+
{ include = "switchbot" },
|
|
11
|
+
]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Other Environment",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Topic :: Home Automation",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.poetry.urls]
|
|
23
|
+
"Bug Tracker" = "https://github.com/sblibs/pySwitchbot/issues"
|
|
24
|
+
"Changelog" = "https://github.com/sblibs/pySwitchbot/blob/main/CHANGELOG.md"
|
|
25
|
+
|
|
26
|
+
[tool.poetry.dependencies]
|
|
27
|
+
python = ">=3.11,<4.0"
|
|
28
|
+
aiohttp = ">=3.9.5"
|
|
29
|
+
bleak = ">=0.19.0"
|
|
30
|
+
bleak-retry-connector = ">=3.4.0"
|
|
31
|
+
cryptography = ">=39.0.0"
|
|
32
|
+
pyOpenSSL = ">=23.0.0"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.group.dev.dependencies]
|
|
35
|
+
pytest = ">=7,<10"
|
|
36
|
+
pytest-cov = ">=3,<8"
|
|
37
|
+
pytest-asyncio = ">=0.19,<1.4"
|
|
38
|
+
|
|
39
|
+
[tool.semantic_release]
|
|
40
|
+
branch = "main"
|
|
41
|
+
# Existing release tags have no "v" prefix (e.g. 2.2.0), unlike PSR's default
|
|
42
|
+
# "v{version}"; without this PSR finds no prior release and bumps from 0.0.0.
|
|
43
|
+
tag_format = "{version}"
|
|
44
|
+
version_toml = ["pyproject.toml:tool.poetry.version"]
|
|
45
|
+
version_variables = ["switchbot/__version__.py:__version__"]
|
|
46
|
+
build_command = "pip install poetry && poetry build"
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
addopts = "--cov=switchbot --cov-report=term-missing"
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["poetry-core>=1.0.0"]
|
|
53
|
+
build-backend = "poetry.core.masonry.api"
|
|
54
|
+
|
|
1
55
|
[tool.ruff]
|
|
2
56
|
target-version = "py311"
|
|
3
57
|
line-length = 88
|
|
4
58
|
|
|
5
59
|
[tool.ruff.lint]
|
|
60
|
+
preview = true
|
|
61
|
+
explicit-preview-rules = true # opt in ONLY explicitly-listed preview rules
|
|
6
62
|
ignore = [
|
|
7
63
|
"S101", # use of assert
|
|
8
64
|
"D203", # 1 blank line required before class docstring
|
|
@@ -34,15 +90,30 @@ ignore = [
|
|
|
34
90
|
]
|
|
35
91
|
select = [
|
|
36
92
|
"ASYNC", # async rules
|
|
93
|
+
"A", # flake8-builtins
|
|
37
94
|
"B", # flake8-bugbear
|
|
95
|
+
"BLE", # flake8-blind-except
|
|
38
96
|
"D", # flake8-docstrings
|
|
39
97
|
"C4", # flake8-comprehensions
|
|
98
|
+
"C90", # mccabe complexity
|
|
99
|
+
"DTZ", # flake8-datetimez
|
|
100
|
+
"ERA", # eradicate
|
|
101
|
+
"EXE", # flake8-executable
|
|
40
102
|
"S", # flake8-bandit
|
|
41
103
|
"F", # pyflake
|
|
104
|
+
"FA", # flake8-future-annotations
|
|
105
|
+
"FIX", # flake8-fixme
|
|
106
|
+
"FURB", # refurb
|
|
107
|
+
"FURB118", # reimplemented-operator (preview) - unneeded lambdas
|
|
42
108
|
"E", # pycodestyle
|
|
43
109
|
"W", # pycodestyle
|
|
44
110
|
"UP", # pyupgrade
|
|
45
111
|
"I", # isort
|
|
112
|
+
"ICN", # flake8-import-conventions
|
|
113
|
+
"INP", # flake8-no-pep420
|
|
114
|
+
"ISC", # flake8-implicit-str-concat
|
|
115
|
+
"LOG", # flake8-logging
|
|
116
|
+
"Q", # flake8-quotes
|
|
46
117
|
"RUF", # ruff specific
|
|
47
118
|
"FLY", # flynt
|
|
48
119
|
"G", # flake8-logging-format ,
|
|
@@ -60,8 +131,10 @@ select = [
|
|
|
60
131
|
"SLOT", # flake8-slots
|
|
61
132
|
"T100", # Trace found: {name} used
|
|
62
133
|
"T20", # flake8-print
|
|
134
|
+
"TD", # flake8-todos
|
|
63
135
|
"TID", # Tidy imports
|
|
64
136
|
"TRY", # tryceratops
|
|
137
|
+
"YTT", # flake8-2020
|
|
65
138
|
]
|
|
66
139
|
|
|
67
140
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -18,16 +18,20 @@ from .const import (
|
|
|
18
18
|
ClimateMode,
|
|
19
19
|
ColorMode,
|
|
20
20
|
FanMode,
|
|
21
|
+
HorizontalOscillationAngle,
|
|
21
22
|
HumidifierAction,
|
|
22
23
|
HumidifierMode,
|
|
23
24
|
HumidifierWaterLevel,
|
|
24
25
|
LockStatus,
|
|
26
|
+
NightLightState,
|
|
25
27
|
SmartThermostatRadiatorMode,
|
|
28
|
+
StandingFanMode,
|
|
26
29
|
StripLightColorMode,
|
|
27
30
|
SwitchbotAccountConnectionError,
|
|
28
31
|
SwitchbotApiError,
|
|
29
32
|
SwitchbotAuthenticationError,
|
|
30
33
|
SwitchbotModel,
|
|
34
|
+
VerticalOscillationAngle,
|
|
31
35
|
)
|
|
32
36
|
from .devices.air_purifier import SwitchbotAirPurifier
|
|
33
37
|
from .devices.art_frame import SwitchbotArtFrame
|
|
@@ -44,12 +48,16 @@ from .devices.device import (
|
|
|
44
48
|
fetch_cloud_devices,
|
|
45
49
|
)
|
|
46
50
|
from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
|
|
47
|
-
from .devices.fan import SwitchbotFan
|
|
51
|
+
from .devices.fan import SwitchbotFan, SwitchbotStandingFan
|
|
48
52
|
from .devices.humidifier import SwitchbotHumidifier
|
|
49
53
|
from .devices.keypad_vision import SwitchbotKeypadVision
|
|
50
54
|
from .devices.light_strip import (
|
|
55
|
+
SwitchbotCandleWarmerLamp,
|
|
51
56
|
SwitchbotLightStrip,
|
|
57
|
+
SwitchbotPermanentOutdoorLight,
|
|
52
58
|
SwitchbotRgbicLight,
|
|
59
|
+
SwitchbotRgbicNeonLight,
|
|
60
|
+
SwitchbotRgbicwwCeilingLight,
|
|
53
61
|
SwitchbotStripLight3,
|
|
54
62
|
)
|
|
55
63
|
from .devices.lock import SwitchbotLock
|
|
@@ -76,11 +84,14 @@ __all__ = [
|
|
|
76
84
|
"ColorMode",
|
|
77
85
|
"FanMode",
|
|
78
86
|
"GetSwitchbotDevices",
|
|
87
|
+
"HorizontalOscillationAngle",
|
|
79
88
|
"HumidifierAction",
|
|
80
89
|
"HumidifierMode",
|
|
81
90
|
"HumidifierWaterLevel",
|
|
82
91
|
"LockStatus",
|
|
92
|
+
"NightLightState",
|
|
83
93
|
"SmartThermostatRadiatorMode",
|
|
94
|
+
"StandingFanMode",
|
|
84
95
|
"StripLightColorMode",
|
|
85
96
|
"SwitchBotAdvertisement",
|
|
86
97
|
"Switchbot",
|
|
@@ -93,6 +104,7 @@ __all__ = [
|
|
|
93
104
|
"SwitchbotBaseLight",
|
|
94
105
|
"SwitchbotBlindTilt",
|
|
95
106
|
"SwitchbotBulb",
|
|
107
|
+
"SwitchbotCandleWarmerLamp",
|
|
96
108
|
"SwitchbotCeilingLight",
|
|
97
109
|
"SwitchbotCurtain",
|
|
98
110
|
"SwitchbotDevice",
|
|
@@ -108,17 +120,22 @@ __all__ = [
|
|
|
108
120
|
"SwitchbotModel",
|
|
109
121
|
"SwitchbotModel",
|
|
110
122
|
"SwitchbotOperationError",
|
|
123
|
+
"SwitchbotPermanentOutdoorLight",
|
|
111
124
|
"SwitchbotPlugMini",
|
|
112
125
|
"SwitchbotPlugMini",
|
|
113
126
|
"SwitchbotRelaySwitch",
|
|
114
127
|
"SwitchbotRelaySwitch2PM",
|
|
115
128
|
"SwitchbotRgbicLight",
|
|
129
|
+
"SwitchbotRgbicNeonLight",
|
|
130
|
+
"SwitchbotRgbicwwCeilingLight",
|
|
116
131
|
"SwitchbotRollerShade",
|
|
117
132
|
"SwitchbotSmartThermostatRadiator",
|
|
133
|
+
"SwitchbotStandingFan",
|
|
118
134
|
"SwitchbotStripLight3",
|
|
119
135
|
"SwitchbotSupportedType",
|
|
120
136
|
"SwitchbotSupportedType",
|
|
121
137
|
"SwitchbotVacuum",
|
|
138
|
+
"VerticalOscillationAngle",
|
|
122
139
|
"close_stale_connections",
|
|
123
140
|
"close_stale_connections_by_address",
|
|
124
141
|
"fetch_cloud_devices",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.0"
|
|
@@ -20,7 +20,7 @@ from .adv_parsers.ceiling_light import process_woceiling
|
|
|
20
20
|
from .adv_parsers.climate_panel import process_climate_panel
|
|
21
21
|
from .adv_parsers.contact import process_wocontact
|
|
22
22
|
from .adv_parsers.curtain import process_wocurtain
|
|
23
|
-
from .adv_parsers.fan import process_fan
|
|
23
|
+
from .adv_parsers.fan import process_fan, process_standing_fan
|
|
24
24
|
from .adv_parsers.hub2 import process_wohub2
|
|
25
25
|
from .adv_parsers.hub3 import process_hub3
|
|
26
26
|
from .adv_parsers.hubmini_matter import process_hubmini_matter
|
|
@@ -28,7 +28,13 @@ from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohu
|
|
|
28
28
|
from .adv_parsers.keypad import process_wokeypad
|
|
29
29
|
from .adv_parsers.keypad_vision import process_keypad_vision, process_keypad_vision_pro
|
|
30
30
|
from .adv_parsers.leak import process_leak
|
|
31
|
-
from .adv_parsers.light_strip import
|
|
31
|
+
from .adv_parsers.light_strip import (
|
|
32
|
+
process_candle_warmer_lamp,
|
|
33
|
+
process_light,
|
|
34
|
+
process_rgbic_light,
|
|
35
|
+
process_rgbicww_ceiling_light,
|
|
36
|
+
process_wostrip,
|
|
37
|
+
)
|
|
32
38
|
from .adv_parsers.lock import (
|
|
33
39
|
process_lock2,
|
|
34
40
|
process_locklite,
|
|
@@ -49,6 +55,7 @@ from .adv_parsers.remote import process_woremote
|
|
|
49
55
|
from .adv_parsers.roller_shade import process_worollershade
|
|
50
56
|
from .adv_parsers.smart_thermostat_radiator import process_smart_thermostat_radiator
|
|
51
57
|
from .adv_parsers.vacuum import process_vacuum, process_vacuum_k
|
|
58
|
+
from .adv_parsers.weather_station import process_weather_station
|
|
52
59
|
from .const import SwitchbotModel
|
|
53
60
|
from .models import SwitchBotAdvertisement
|
|
54
61
|
from .utils import format_mac_upper
|
|
@@ -69,7 +76,7 @@ class SwitchbotSupportedType(TypedDict):
|
|
|
69
76
|
|
|
70
77
|
modelName: SwitchbotModel
|
|
71
78
|
modelFriendlyName: str
|
|
72
|
-
func: Callable[[bytes, bytes | None], dict[str, bool | int]]
|
|
79
|
+
func: Callable[[bytes | None, bytes | None], dict[str, bool | int | str | None]]
|
|
73
80
|
manufacturer_id: int | None
|
|
74
81
|
manufacturer_data_length: int | None
|
|
75
82
|
|
|
@@ -611,6 +618,18 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
|
|
|
611
618
|
"func": process_light,
|
|
612
619
|
"manufacturer_id": 2409,
|
|
613
620
|
},
|
|
621
|
+
b"\x00\x11\x22\xb8": {
|
|
622
|
+
"modelName": SwitchbotModel.CANDLE_WARMER_LAMP,
|
|
623
|
+
"modelFriendlyName": "Candle Warmer Lamp",
|
|
624
|
+
"func": process_candle_warmer_lamp,
|
|
625
|
+
"manufacturer_id": 2409,
|
|
626
|
+
},
|
|
627
|
+
b"\x01\x11\x22\xb8": {
|
|
628
|
+
"modelName": SwitchbotModel.CANDLE_WARMER_LAMP,
|
|
629
|
+
"modelFriendlyName": "Candle Warmer Lamp",
|
|
630
|
+
"func": process_candle_warmer_lamp,
|
|
631
|
+
"manufacturer_id": 2409,
|
|
632
|
+
},
|
|
614
633
|
b"\x00\x10\xd0\xb1": {
|
|
615
634
|
"modelName": SwitchbotModel.STRIP_LIGHT_3,
|
|
616
635
|
"modelFriendlyName": "Strip Light 3",
|
|
@@ -659,6 +678,54 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
|
|
|
659
678
|
"func": process_rgbic_light,
|
|
660
679
|
"manufacturer_id": 2409,
|
|
661
680
|
},
|
|
681
|
+
b"\x00\x11\xbb\x10": {
|
|
682
|
+
"modelName": SwitchbotModel.RGBICWW_CEILING_LIGHT,
|
|
683
|
+
"modelFriendlyName": "RGBICWW Ceiling Light",
|
|
684
|
+
"func": process_rgbicww_ceiling_light,
|
|
685
|
+
"manufacturer_id": 2409,
|
|
686
|
+
},
|
|
687
|
+
b"\x01\x11\xbb\x10": {
|
|
688
|
+
"modelName": SwitchbotModel.RGBICWW_CEILING_LIGHT,
|
|
689
|
+
"modelFriendlyName": "RGBICWW Ceiling Light",
|
|
690
|
+
"func": process_rgbicww_ceiling_light,
|
|
691
|
+
"manufacturer_id": 2409,
|
|
692
|
+
},
|
|
693
|
+
b"\x00\x10\xd0\xb7": {
|
|
694
|
+
"modelName": SwitchbotModel.PERMANENT_OUTDOOR_LIGHT,
|
|
695
|
+
"modelFriendlyName": "Permanent Outdoor Light",
|
|
696
|
+
"func": process_rgbic_light,
|
|
697
|
+
"manufacturer_id": 2409,
|
|
698
|
+
},
|
|
699
|
+
b"\x01\x10\xd0\xb7": {
|
|
700
|
+
"modelName": SwitchbotModel.PERMANENT_OUTDOOR_LIGHT,
|
|
701
|
+
"modelFriendlyName": "Permanent Outdoor Light",
|
|
702
|
+
"func": process_rgbic_light,
|
|
703
|
+
"manufacturer_id": 2409,
|
|
704
|
+
},
|
|
705
|
+
b"\x00\x10\xd0\xb5": {
|
|
706
|
+
"modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT,
|
|
707
|
+
"modelFriendlyName": "RGBIC Neon Wire Rope Light",
|
|
708
|
+
"func": process_wostrip,
|
|
709
|
+
"manufacturer_id": 2409,
|
|
710
|
+
},
|
|
711
|
+
b"\x00\x10\xd0\xb6": {
|
|
712
|
+
"modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT,
|
|
713
|
+
"modelFriendlyName": "RGBIC Neon Rope Light",
|
|
714
|
+
"func": process_wostrip,
|
|
715
|
+
"manufacturer_id": 2409,
|
|
716
|
+
},
|
|
717
|
+
b"\x01\x10\xd0\xb5": {
|
|
718
|
+
"modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT,
|
|
719
|
+
"modelFriendlyName": "RGBIC Neon Wire Rope Light",
|
|
720
|
+
"func": process_wostrip,
|
|
721
|
+
"manufacturer_id": 2409,
|
|
722
|
+
},
|
|
723
|
+
b"\x01\x10\xd0\xb6": {
|
|
724
|
+
"modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT,
|
|
725
|
+
"modelFriendlyName": "RGBIC Neon Rope Light",
|
|
726
|
+
"func": process_wostrip,
|
|
727
|
+
"manufacturer_id": 2409,
|
|
728
|
+
},
|
|
662
729
|
b"\x00\x10\xfb\xa8": {
|
|
663
730
|
"modelName": SwitchbotModel.K11_VACUUM,
|
|
664
731
|
"modelFriendlyName": "K11+ Vacuum",
|
|
@@ -791,6 +858,30 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
|
|
|
791
858
|
"func": process_wolock_pro,
|
|
792
859
|
"manufacturer_id": 2409,
|
|
793
860
|
},
|
|
861
|
+
b"\x00\x11\x07\x60": {
|
|
862
|
+
"modelName": SwitchbotModel.STANDING_FAN,
|
|
863
|
+
"modelFriendlyName": "Standing Fan",
|
|
864
|
+
"func": process_standing_fan,
|
|
865
|
+
"manufacturer_id": 2409,
|
|
866
|
+
},
|
|
867
|
+
b"\x01\x11\x07\x60": {
|
|
868
|
+
"modelName": SwitchbotModel.STANDING_FAN,
|
|
869
|
+
"modelFriendlyName": "Standing Fan",
|
|
870
|
+
"func": process_standing_fan,
|
|
871
|
+
"manufacturer_id": 2409,
|
|
872
|
+
},
|
|
873
|
+
b"\x00\x10\x53\xb0": {
|
|
874
|
+
"modelName": SwitchbotModel.WEATHER_STATION,
|
|
875
|
+
"modelFriendlyName": "Weather Station",
|
|
876
|
+
"func": process_weather_station,
|
|
877
|
+
"manufacturer_id": 2409,
|
|
878
|
+
},
|
|
879
|
+
b"\x01\x10\x53\xb0": {
|
|
880
|
+
"modelName": SwitchbotModel.WEATHER_STATION,
|
|
881
|
+
"modelFriendlyName": "Weather Station",
|
|
882
|
+
"func": process_weather_station,
|
|
883
|
+
"manufacturer_id": 2409,
|
|
884
|
+
},
|
|
794
885
|
}
|
|
795
886
|
|
|
796
887
|
_SWITCHBOT_MODEL_TO_CHAR: defaultdict[SwitchbotModel, list[str | bytes]] = defaultdict(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Shared temperature/humidity decoding helpers for T/H sensors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..helpers import celsius_to_fahrenheit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decode_temp_humidity(temp_data: bytes, battery: int | None) -> dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
Decode temperature/humidity/fahrenheit-flag from a 3-byte payload.
|
|
13
|
+
|
|
14
|
+
Layout (bytes after company ID, for SwitchBot T/H sensors):
|
|
15
|
+
byte 0: bits[3:0] = temperature decimal (0.1 °C units)
|
|
16
|
+
byte 1: bit[7] = temperature sign (1 = positive), bits[6:0] = integer °C
|
|
17
|
+
byte 2: bit[7] = fahrenheit-display flag, bits[6:0] = humidity %
|
|
18
|
+
"""
|
|
19
|
+
_temp_sign = 1 if temp_data[1] & 0b10000000 else -1
|
|
20
|
+
_temp_c = _temp_sign * (
|
|
21
|
+
(temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
|
|
22
|
+
)
|
|
23
|
+
_temp_f = celsius_to_fahrenheit(_temp_c)
|
|
24
|
+
_temp_f = (_temp_f * 10) / 10
|
|
25
|
+
humidity = temp_data[2] & 0b01111111
|
|
26
|
+
|
|
27
|
+
if _temp_c == 0 and humidity == 0 and battery == 0:
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
"temp": {"c": _temp_c, "f": _temp_f},
|
|
32
|
+
"temperature": _temp_c,
|
|
33
|
+
"fahrenheit": bool(temp_data[2] & 0b10000000),
|
|
34
|
+
"humidity": humidity,
|
|
35
|
+
"battery": battery,
|
|
36
|
+
}
|
|
@@ -11,7 +11,7 @@ def process_air_purifier(
|
|
|
11
11
|
data: bytes | None, mfr_data: bytes | None
|
|
12
12
|
) -> dict[str, bool | int]:
|
|
13
13
|
"""Process air purifier services data."""
|
|
14
|
-
if mfr_data is None:
|
|
14
|
+
if mfr_data is None or len(mfr_data) < 14:
|
|
15
15
|
return {}
|
|
16
16
|
device_data = mfr_data[6:]
|
|
17
17
|
|
|
@@ -7,7 +7,7 @@ def process_woblindtilt(
|
|
|
7
7
|
data: bytes | None, mfr_data: bytes | None, reverse: bool = False
|
|
8
8
|
) -> dict[str, bool | int]:
|
|
9
9
|
"""Process woBlindTilt services data."""
|
|
10
|
-
if mfr_data is None:
|
|
10
|
+
if mfr_data is None or len(mfr_data) < 10:
|
|
11
11
|
return {}
|
|
12
12
|
|
|
13
13
|
device_data = mfr_data[6:]
|
|
@@ -19,7 +19,7 @@ def process_woblindtilt(
|
|
|
19
19
|
|
|
20
20
|
return {
|
|
21
21
|
"calibration": _calibrated,
|
|
22
|
-
"battery": data[2] & 0b01111111 if data else None,
|
|
22
|
+
"battery": data[2] & 0b01111111 if data and len(data) >= 3 else None,
|
|
23
23
|
"inMotion": _in_motion,
|
|
24
24
|
"tilt": (100 - _tilt) if reverse else _tilt,
|
|
25
25
|
"lightLevel": _light_level,
|
|
@@ -7,7 +7,7 @@ def process_color_bulb(
|
|
|
7
7
|
data: bytes | None, mfr_data: bytes | None
|
|
8
8
|
) -> dict[str, bool | int]:
|
|
9
9
|
"""Process WoBulb services data."""
|
|
10
|
-
if mfr_data is None:
|
|
10
|
+
if mfr_data is None or len(mfr_data) < 11:
|
|
11
11
|
return {}
|
|
12
12
|
return {
|
|
13
13
|
"sequence_number": mfr_data[6],
|
|
@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
def process_woceiling(data: bytes, mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
17
17
|
"""Process WoCeiling services data."""
|
|
18
|
-
if mfr_data is None:
|
|
18
|
+
if mfr_data is None or len(mfr_data) < 11:
|
|
19
19
|
return {}
|
|
20
20
|
return {
|
|
21
21
|
"sequence_number": mfr_data[6],
|
|
@@ -10,10 +10,15 @@ def process_wocontact(
|
|
|
10
10
|
if data is None and mfr_data is None:
|
|
11
11
|
return {}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
has_full_mfr = mfr_data is not None and len(mfr_data) >= 13
|
|
14
|
+
has_full_data = data is not None and len(data) >= 9
|
|
15
|
+
if not has_full_mfr and not has_full_data:
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
battery = data[2] & 0b01111111 if data and len(data) >= 3 else None
|
|
19
|
+
tested = bool(data[1] & 0b10000000) if data and len(data) >= 2 else None
|
|
15
20
|
|
|
16
|
-
if
|
|
21
|
+
if has_full_mfr:
|
|
17
22
|
motion_detected = bool(mfr_data[7] & 0b10000000)
|
|
18
23
|
contact_open = bool(mfr_data[7] & 0b00010000)
|
|
19
24
|
contact_timeout = bool(mfr_data[7] & 0b00100000)
|