PySwitchbot 0.58.0__tar.gz → 0.60.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-0.60.0/PKG-INFO +135 -0
- pyswitchbot-0.60.0/PySwitchbot.egg-info/PKG-INFO +135 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/PySwitchbot.egg-info/SOURCES.txt +9 -1
- pyswitchbot-0.60.0/README.md +102 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/setup.py +1 -1
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/__init__.py +6 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parser.py +21 -0
- pyswitchbot-0.60.0/switchbot/adv_parsers/fan.py +33 -0
- pyswitchbot-0.60.0/switchbot/adv_parsers/hubmini_matter.py +37 -0
- pyswitchbot-0.60.0/switchbot/adv_parsers/roller_shade.py +29 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/const/__init__.py +4 -0
- pyswitchbot-0.60.0/switchbot/const/fan.py +14 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/base_cover.py +2 -0
- pyswitchbot-0.60.0/switchbot/devices/fan.py +116 -0
- pyswitchbot-0.60.0/switchbot/devices/roller_shade.py +129 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/discovery.py +8 -1
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_adv_parser.py +226 -0
- pyswitchbot-0.60.0/tests/test_fan.py +172 -0
- pyswitchbot-0.60.0/tests/test_roller_shade.py +223 -0
- pyswitchbot-0.58.0/PKG-INFO +0 -70
- pyswitchbot-0.58.0/PySwitchbot.egg-info/PKG-INFO +0 -70
- pyswitchbot-0.58.0/README.md +0 -37
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/LICENSE +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/MANIFEST.in +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/pyproject.toml +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/setup.cfg +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/hub2.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/humidifier.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/keypad.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/leak.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/lock.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/meter.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/plug.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/relay_switch.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/adv_parsers/remote.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/const/evaporative_humidifier.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/const/hub2.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/const/lock.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/device.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/evaporative_humidifier.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/lock.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/devices/relay_switch.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/enum.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/helpers.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/switchbot/models.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_base_cover.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_blind_tilt.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_curtain.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_evaporative_humidifier.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_hub2.py +0 -0
- {pyswitchbot-0.58.0 → pyswitchbot-0.60.0}/tests/test_relay_switch.py +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PySwitchbot
|
|
3
|
+
Version: 0.60.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
|
+
# pySwitchbot [](https://travis-ci.org/sblibs/pySwitchbot)
|
|
35
|
+
|
|
36
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/bot
|
|
37
|
+
|
|
38
|
+
## Obtaining encryption key for Switchbot Locks
|
|
39
|
+
|
|
40
|
+
Using the script `scripts/get_encryption_key.py` you can manually obtain locks encryption key.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
$ python3 get_encryption_key.py MAC USERNAME
|
|
46
|
+
Key ID: XX
|
|
47
|
+
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Where `MAC` is MAC address of the lock and `USERNAME` is your SwitchBot account username, after that script will ask for your password.
|
|
51
|
+
If authentication succeeds then script should output your key id and encryption key.
|
|
52
|
+
|
|
53
|
+
## Examples:
|
|
54
|
+
|
|
55
|
+
#### WoLock (Lock-Pro)
|
|
56
|
+
|
|
57
|
+
Unlock:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
62
|
+
from switchbot.devices import lock
|
|
63
|
+
from switchbot.const import SwitchbotModel
|
|
64
|
+
|
|
65
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
66
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
67
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
68
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
73
|
+
await lock.SwitchbotLock(
|
|
74
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
75
|
+
).unlock()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Lock:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import asyncio
|
|
85
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
86
|
+
from switchbot.devices import lock
|
|
87
|
+
from switchbot.const import SwitchbotModel
|
|
88
|
+
|
|
89
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
90
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
91
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
92
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def main():
|
|
96
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
97
|
+
await lock.SwitchbotLock(
|
|
98
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
99
|
+
).lock()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### WoCurtain (Curtain 3)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import asyncio
|
|
109
|
+
from pprint import pprint
|
|
110
|
+
from switchbot import GetSwitchbotDevices
|
|
111
|
+
from switchbot.devices import curtain
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
# get the BLE advertisement data of all switchbot devices in the vicinity
|
|
116
|
+
advertisement_data = await GetSwitchbotDevices().discover()
|
|
117
|
+
|
|
118
|
+
for i in advertisement_data.values():
|
|
119
|
+
pprint(i)
|
|
120
|
+
print() # print newline so that devices' data is separated visually
|
|
121
|
+
|
|
122
|
+
# find your device's BLE address by inspecting the above printed debug logs, example below
|
|
123
|
+
ble_address = "9915077C-C6FD-5FF6-27D3-45087898790B"
|
|
124
|
+
# get the BLE device (via its address) and construct a curtain device
|
|
125
|
+
ble_device = advertisement_data[ble_address].device
|
|
126
|
+
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=False)
|
|
127
|
+
|
|
128
|
+
pprint(await curtain_device.get_device_data())
|
|
129
|
+
pprint(await curtain_device.get_basic_info())
|
|
130
|
+
await curtain_device.set_position(100)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PySwitchbot
|
|
3
|
+
Version: 0.60.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
|
+
# pySwitchbot [](https://travis-ci.org/sblibs/pySwitchbot)
|
|
35
|
+
|
|
36
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/bot
|
|
37
|
+
|
|
38
|
+
## Obtaining encryption key for Switchbot Locks
|
|
39
|
+
|
|
40
|
+
Using the script `scripts/get_encryption_key.py` you can manually obtain locks encryption key.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
$ python3 get_encryption_key.py MAC USERNAME
|
|
46
|
+
Key ID: XX
|
|
47
|
+
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Where `MAC` is MAC address of the lock and `USERNAME` is your SwitchBot account username, after that script will ask for your password.
|
|
51
|
+
If authentication succeeds then script should output your key id and encryption key.
|
|
52
|
+
|
|
53
|
+
## Examples:
|
|
54
|
+
|
|
55
|
+
#### WoLock (Lock-Pro)
|
|
56
|
+
|
|
57
|
+
Unlock:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
62
|
+
from switchbot.devices import lock
|
|
63
|
+
from switchbot.const import SwitchbotModel
|
|
64
|
+
|
|
65
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
66
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
67
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
68
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
73
|
+
await lock.SwitchbotLock(
|
|
74
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
75
|
+
).unlock()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Lock:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import asyncio
|
|
85
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
86
|
+
from switchbot.devices import lock
|
|
87
|
+
from switchbot.const import SwitchbotModel
|
|
88
|
+
|
|
89
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
90
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
91
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
92
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def main():
|
|
96
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
97
|
+
await lock.SwitchbotLock(
|
|
98
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
99
|
+
).lock()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### WoCurtain (Curtain 3)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import asyncio
|
|
109
|
+
from pprint import pprint
|
|
110
|
+
from switchbot import GetSwitchbotDevices
|
|
111
|
+
from switchbot.devices import curtain
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
# get the BLE advertisement data of all switchbot devices in the vicinity
|
|
116
|
+
advertisement_data = await GetSwitchbotDevices().discover()
|
|
117
|
+
|
|
118
|
+
for i in advertisement_data.values():
|
|
119
|
+
pprint(i)
|
|
120
|
+
print() # print newline so that devices' data is separated visually
|
|
121
|
+
|
|
122
|
+
# find your device's BLE address by inspecting the above printed debug logs, example below
|
|
123
|
+
ble_address = "9915077C-C6FD-5FF6-27D3-45087898790B"
|
|
124
|
+
# get the BLE device (via its address) and construct a curtain device
|
|
125
|
+
ble_device = advertisement_data[ble_address].device
|
|
126
|
+
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=False)
|
|
127
|
+
|
|
128
|
+
pprint(await curtain_device.get_device_data())
|
|
129
|
+
pprint(await curtain_device.get_basic_info())
|
|
130
|
+
await curtain_device.set_position(100)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
@@ -22,7 +22,9 @@ switchbot/adv_parsers/bulb.py
|
|
|
22
22
|
switchbot/adv_parsers/ceiling_light.py
|
|
23
23
|
switchbot/adv_parsers/contact.py
|
|
24
24
|
switchbot/adv_parsers/curtain.py
|
|
25
|
+
switchbot/adv_parsers/fan.py
|
|
25
26
|
switchbot/adv_parsers/hub2.py
|
|
27
|
+
switchbot/adv_parsers/hubmini_matter.py
|
|
26
28
|
switchbot/adv_parsers/humidifier.py
|
|
27
29
|
switchbot/adv_parsers/keypad.py
|
|
28
30
|
switchbot/adv_parsers/leak.py
|
|
@@ -33,8 +35,10 @@ switchbot/adv_parsers/motion.py
|
|
|
33
35
|
switchbot/adv_parsers/plug.py
|
|
34
36
|
switchbot/adv_parsers/relay_switch.py
|
|
35
37
|
switchbot/adv_parsers/remote.py
|
|
38
|
+
switchbot/adv_parsers/roller_shade.py
|
|
36
39
|
switchbot/const/__init__.py
|
|
37
40
|
switchbot/const/evaporative_humidifier.py
|
|
41
|
+
switchbot/const/fan.py
|
|
38
42
|
switchbot/const/hub2.py
|
|
39
43
|
switchbot/const/lock.py
|
|
40
44
|
switchbot/devices/__init__.py
|
|
@@ -48,6 +52,7 @@ switchbot/devices/contact.py
|
|
|
48
52
|
switchbot/devices/curtain.py
|
|
49
53
|
switchbot/devices/device.py
|
|
50
54
|
switchbot/devices/evaporative_humidifier.py
|
|
55
|
+
switchbot/devices/fan.py
|
|
51
56
|
switchbot/devices/humidifier.py
|
|
52
57
|
switchbot/devices/keypad.py
|
|
53
58
|
switchbot/devices/light_strip.py
|
|
@@ -56,10 +61,13 @@ switchbot/devices/meter.py
|
|
|
56
61
|
switchbot/devices/motion.py
|
|
57
62
|
switchbot/devices/plug.py
|
|
58
63
|
switchbot/devices/relay_switch.py
|
|
64
|
+
switchbot/devices/roller_shade.py
|
|
59
65
|
tests/test_adv_parser.py
|
|
60
66
|
tests/test_base_cover.py
|
|
61
67
|
tests/test_blind_tilt.py
|
|
62
68
|
tests/test_curtain.py
|
|
63
69
|
tests/test_evaporative_humidifier.py
|
|
70
|
+
tests/test_fan.py
|
|
64
71
|
tests/test_hub2.py
|
|
65
|
-
tests/test_relay_switch.py
|
|
72
|
+
tests/test_relay_switch.py
|
|
73
|
+
tests/test_roller_shade.py
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# pySwitchbot [](https://travis-ci.org/sblibs/pySwitchbot)
|
|
2
|
+
|
|
3
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/bot
|
|
4
|
+
|
|
5
|
+
## Obtaining encryption key for Switchbot Locks
|
|
6
|
+
|
|
7
|
+
Using the script `scripts/get_encryption_key.py` you can manually obtain locks encryption key.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
$ python3 get_encryption_key.py MAC USERNAME
|
|
13
|
+
Key ID: XX
|
|
14
|
+
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Where `MAC` is MAC address of the lock and `USERNAME` is your SwitchBot account username, after that script will ask for your password.
|
|
18
|
+
If authentication succeeds then script should output your key id and encryption key.
|
|
19
|
+
|
|
20
|
+
## Examples:
|
|
21
|
+
|
|
22
|
+
#### WoLock (Lock-Pro)
|
|
23
|
+
|
|
24
|
+
Unlock:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import asyncio
|
|
28
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
29
|
+
from switchbot.devices import lock
|
|
30
|
+
from switchbot.const import SwitchbotModel
|
|
31
|
+
|
|
32
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
33
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
34
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
35
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def main():
|
|
39
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
40
|
+
await lock.SwitchbotLock(
|
|
41
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
42
|
+
).unlock()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
asyncio.run(main())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Lock:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from switchbot.discovery import GetSwitchbotDevices
|
|
53
|
+
from switchbot.devices import lock
|
|
54
|
+
from switchbot.const import SwitchbotModel
|
|
55
|
+
|
|
56
|
+
BLE_MAC="XX:XX:XX:XX:XX:XX" # The MAC of your lock
|
|
57
|
+
KEY_ID="XX" # The key-ID of your encryption-key for your lock
|
|
58
|
+
ENC_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # The encryption-key with key-ID "XX"
|
|
59
|
+
LOCK_MODEL=SwitchbotModel.LOCK_PRO # Your lock model (here we use the Lock-Pro)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def main():
|
|
63
|
+
wolock = await GetSwitchbotDevices().get_locks()
|
|
64
|
+
await lock.SwitchbotLock(
|
|
65
|
+
wolock[BLE_MAC].device, KEY_ID, ENCRYPTION_KEY, model=LOCK_MODEL
|
|
66
|
+
).lock()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### WoCurtain (Curtain 3)
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import asyncio
|
|
76
|
+
from pprint import pprint
|
|
77
|
+
from switchbot import GetSwitchbotDevices
|
|
78
|
+
from switchbot.devices import curtain
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
# get the BLE advertisement data of all switchbot devices in the vicinity
|
|
83
|
+
advertisement_data = await GetSwitchbotDevices().discover()
|
|
84
|
+
|
|
85
|
+
for i in advertisement_data.values():
|
|
86
|
+
pprint(i)
|
|
87
|
+
print() # print newline so that devices' data is separated visually
|
|
88
|
+
|
|
89
|
+
# find your device's BLE address by inspecting the above printed debug logs, example below
|
|
90
|
+
ble_address = "9915077C-C6FD-5FF6-27D3-45087898790B"
|
|
91
|
+
# get the BLE device (via its address) and construct a curtain device
|
|
92
|
+
ble_device = advertisement_data[ble_address].device
|
|
93
|
+
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=False)
|
|
94
|
+
|
|
95
|
+
pprint(await curtain_device.get_device_data())
|
|
96
|
+
pprint(await curtain_device.get_basic_info())
|
|
97
|
+
await curtain_device.set_position(100)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
asyncio.run(main())
|
|
102
|
+
```
|
|
@@ -10,6 +10,7 @@ from bleak_retry_connector import (
|
|
|
10
10
|
|
|
11
11
|
from .adv_parser import SwitchbotSupportedType, parse_advertisement_data
|
|
12
12
|
from .const import (
|
|
13
|
+
FanMode,
|
|
13
14
|
LockStatus,
|
|
14
15
|
SwitchbotAccountConnectionError,
|
|
15
16
|
SwitchbotApiError,
|
|
@@ -24,16 +25,19 @@ from .devices.ceiling_light import SwitchbotCeilingLight
|
|
|
24
25
|
from .devices.curtain import SwitchbotCurtain
|
|
25
26
|
from .devices.device import ColorMode, SwitchbotDevice, SwitchbotEncryptedDevice
|
|
26
27
|
from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
|
|
28
|
+
from .devices.fan import SwitchbotFan
|
|
27
29
|
from .devices.humidifier import SwitchbotHumidifier
|
|
28
30
|
from .devices.light_strip import SwitchbotLightStrip
|
|
29
31
|
from .devices.lock import SwitchbotLock
|
|
30
32
|
from .devices.plug import SwitchbotPlugMini
|
|
31
33
|
from .devices.relay_switch import SwitchbotRelaySwitch
|
|
34
|
+
from .devices.roller_shade import SwitchbotRollerShade
|
|
32
35
|
from .discovery import GetSwitchbotDevices
|
|
33
36
|
from .models import SwitchBotAdvertisement
|
|
34
37
|
|
|
35
38
|
__all__ = [
|
|
36
39
|
"ColorMode",
|
|
40
|
+
"FanMode",
|
|
37
41
|
"GetSwitchbotDevices",
|
|
38
42
|
"LockStatus",
|
|
39
43
|
"SwitchBotAdvertisement",
|
|
@@ -50,6 +54,7 @@ __all__ = [
|
|
|
50
54
|
"SwitchbotDevice",
|
|
51
55
|
"SwitchbotEncryptedDevice",
|
|
52
56
|
"SwitchbotEvaporativeHumidifier",
|
|
57
|
+
"SwitchbotFan",
|
|
53
58
|
"SwitchbotHumidifier",
|
|
54
59
|
"SwitchbotLightStrip",
|
|
55
60
|
"SwitchbotLock",
|
|
@@ -58,6 +63,7 @@ __all__ = [
|
|
|
58
63
|
"SwitchbotPlugMini",
|
|
59
64
|
"SwitchbotPlugMini",
|
|
60
65
|
"SwitchbotRelaySwitch",
|
|
66
|
+
"SwitchbotRollerShade",
|
|
61
67
|
"SwitchbotSupportedType",
|
|
62
68
|
"SwitchbotSupportedType",
|
|
63
69
|
"close_stale_connections",
|
|
@@ -16,7 +16,9 @@ from .adv_parsers.bulb import process_color_bulb
|
|
|
16
16
|
from .adv_parsers.ceiling_light import process_woceiling
|
|
17
17
|
from .adv_parsers.contact import process_wocontact
|
|
18
18
|
from .adv_parsers.curtain import process_wocurtain
|
|
19
|
+
from .adv_parsers.fan import process_fan
|
|
19
20
|
from .adv_parsers.hub2 import process_wohub2
|
|
21
|
+
from .adv_parsers.hubmini_matter import process_hubmini_matter
|
|
20
22
|
from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohumidifier
|
|
21
23
|
from .adv_parsers.keypad import process_wokeypad
|
|
22
24
|
from .adv_parsers.leak import process_leak
|
|
@@ -30,6 +32,7 @@ from .adv_parsers.relay_switch import (
|
|
|
30
32
|
process_worelay_switch_1pm,
|
|
31
33
|
)
|
|
32
34
|
from .adv_parsers.remote import process_woremote
|
|
35
|
+
from .adv_parsers.roller_shade import process_worollershade
|
|
33
36
|
from .const import SwitchbotModel
|
|
34
37
|
from .models import SwitchBotAdvertisement
|
|
35
38
|
|
|
@@ -216,6 +219,24 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
|
|
|
216
219
|
"func": process_woremote,
|
|
217
220
|
"manufacturer_id": 89,
|
|
218
221
|
},
|
|
222
|
+
",": {
|
|
223
|
+
"modelName": SwitchbotModel.ROLLER_SHADE,
|
|
224
|
+
"modelFriendlyName": "Roller Shade",
|
|
225
|
+
"func": process_worollershade,
|
|
226
|
+
"manufacturer_id": 2409,
|
|
227
|
+
},
|
|
228
|
+
"%": {
|
|
229
|
+
"modelName": SwitchbotModel.HUBMINI_MATTER,
|
|
230
|
+
"modelFriendlyName": "HubMini Matter",
|
|
231
|
+
"func": process_hubmini_matter,
|
|
232
|
+
"manufacturer_id": 2409,
|
|
233
|
+
},
|
|
234
|
+
"~": {
|
|
235
|
+
"modelName": SwitchbotModel.CIRCULATOR_FAN,
|
|
236
|
+
"modelFriendlyName": "Circulator Fan",
|
|
237
|
+
"func": process_fan,
|
|
238
|
+
"manufacturer_id": 2409,
|
|
239
|
+
},
|
|
219
240
|
}
|
|
220
241
|
|
|
221
242
|
_SWITCHBOT_MODEL_TO_CHAR = {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Fan adv parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..const.fan import FanMode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def process_fan(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
9
|
+
"""Process fan services data."""
|
|
10
|
+
if mfr_data is None:
|
|
11
|
+
return {}
|
|
12
|
+
|
|
13
|
+
device_data = mfr_data[6:]
|
|
14
|
+
|
|
15
|
+
_seq_num = device_data[0]
|
|
16
|
+
_isOn = bool(device_data[1] & 0b10000000)
|
|
17
|
+
_mode = (device_data[1] & 0b01110000) >> 4
|
|
18
|
+
_mode = FanMode(_mode).name if 1 <= _mode <= 4 else None
|
|
19
|
+
_nightLight = (device_data[1] & 0b00001100) >> 2
|
|
20
|
+
_oscillate_left_and_right = bool(device_data[1] & 0b00000010)
|
|
21
|
+
_oscillate_up_and_down = bool(device_data[1] & 0b00000001)
|
|
22
|
+
_battery = device_data[2] & 0b01111111
|
|
23
|
+
_speed = device_data[3] & 0b01111111
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"sequence_number": _seq_num,
|
|
27
|
+
"isOn": _isOn,
|
|
28
|
+
"mode": _mode,
|
|
29
|
+
"nightLight": _nightLight,
|
|
30
|
+
"oscillating": _oscillate_left_and_right | _oscillate_up_and_down,
|
|
31
|
+
"battery": _battery,
|
|
32
|
+
"speed": _speed,
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Hubmini matter parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def process_hubmini_matter(
|
|
9
|
+
data: bytes | None, mfr_data: bytes | None
|
|
10
|
+
) -> dict[str, Any]:
|
|
11
|
+
"""Process Hubmini matter sensor manufacturer data."""
|
|
12
|
+
temp_data = None
|
|
13
|
+
|
|
14
|
+
if mfr_data:
|
|
15
|
+
temp_data = mfr_data[13:16]
|
|
16
|
+
|
|
17
|
+
if not temp_data:
|
|
18
|
+
return {}
|
|
19
|
+
|
|
20
|
+
_temp_sign = 1 if temp_data[1] & 0b10000000 else -1
|
|
21
|
+
_temp_c = _temp_sign * (
|
|
22
|
+
(temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
|
|
23
|
+
)
|
|
24
|
+
_temp_f = (_temp_c * 9 / 5) + 32
|
|
25
|
+
_temp_f = (_temp_f * 10) / 10
|
|
26
|
+
humidity = temp_data[2] & 0b01111111
|
|
27
|
+
|
|
28
|
+
if _temp_c == 0 and humidity == 0:
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
paraser_data = {
|
|
32
|
+
"temp": {"c": _temp_c, "f": _temp_f},
|
|
33
|
+
"temperature": _temp_c,
|
|
34
|
+
"fahrenheit": bool(temp_data[2] & 0b10000000),
|
|
35
|
+
"humidity": humidity,
|
|
36
|
+
}
|
|
37
|
+
return paraser_data
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Library to handle connection with Switchbot."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def process_worollershade(
|
|
7
|
+
data: bytes | None, mfr_data: bytes | None, reverse: bool = True
|
|
8
|
+
) -> dict[str, bool | int]:
|
|
9
|
+
"""Process woRollerShade services data."""
|
|
10
|
+
if mfr_data is None:
|
|
11
|
+
return {}
|
|
12
|
+
|
|
13
|
+
device_data = mfr_data[6:]
|
|
14
|
+
|
|
15
|
+
_position = max(min(device_data[2] & 0b01111111, 100), 0)
|
|
16
|
+
_calibrated = bool(device_data[2] & 0b10000000)
|
|
17
|
+
_in_motion = bool(device_data[1] & 0b00000110)
|
|
18
|
+
_light_level = (device_data[3] >> 4) & 0b00001111
|
|
19
|
+
_device_chain = device_data[3] & 0b00001111
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
"calibration": _calibrated,
|
|
23
|
+
"battery": data[2] & 0b01111111 if data else None,
|
|
24
|
+
"inMotion": _in_motion,
|
|
25
|
+
"position": (100 - _position) if reverse else _position,
|
|
26
|
+
"lightLevel": _light_level,
|
|
27
|
+
"deviceChain": _device_chain,
|
|
28
|
+
"sequence_number": device_data[0],
|
|
29
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from ..enum import StrEnum
|
|
6
|
+
from .fan import FanMode as FanMode
|
|
6
7
|
|
|
7
8
|
# Preserve old LockStatus export for backwards compatibility
|
|
8
9
|
from .lock import LockStatus as LockStatus
|
|
@@ -63,3 +64,6 @@ class SwitchbotModel(StrEnum):
|
|
|
63
64
|
RELAY_SWITCH_1 = "Relay Switch 1"
|
|
64
65
|
REMOTE = "WoRemote"
|
|
65
66
|
EVAPORATIVE_HUMIDIFIER = "Evaporative Humidifier"
|
|
67
|
+
ROLLER_SHADE = "Roller Shade"
|
|
68
|
+
HUBMINI_MATTER = "HubMini Matter"
|
|
69
|
+
CIRCULATOR_FAN = "Circulator Fan"
|
|
@@ -10,6 +10,8 @@ from .device import REQ_HEADER, SwitchbotDevice, update_after_operation
|
|
|
10
10
|
|
|
11
11
|
# Cover keys
|
|
12
12
|
COVER_COMMAND = "4501"
|
|
13
|
+
ROLLERSHADE_COMMAND = "4701"
|
|
14
|
+
CONTROL_SOURCE = "00"
|
|
13
15
|
|
|
14
16
|
# For second element of open and close arrs we should add two bytes i.e. ff00
|
|
15
17
|
# First byte [ff] stands for speed (00 or ff - normal, 01 - slow) *
|