pycoway 1.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.
- pycoway-1.3.0/LICENSE +21 -0
- pycoway-1.3.0/PKG-INFO +282 -0
- pycoway-1.3.0/README.md +251 -0
- pycoway-1.3.0/pyproject.toml +78 -0
- pycoway-1.3.0/setup.cfg +4 -0
- pycoway-1.3.0/src/pycoway/__init__.py +31 -0
- pycoway-1.3.0/src/pycoway/__version__.py +1 -0
- pycoway-1.3.0/src/pycoway/account/__init__.py +1 -0
- pycoway-1.3.0/src/pycoway/account/auth.py +365 -0
- pycoway-1.3.0/src/pycoway/account/maintenance.py +146 -0
- pycoway-1.3.0/src/pycoway/client.py +12 -0
- pycoway-1.3.0/src/pycoway/constants.py +59 -0
- pycoway-1.3.0/src/pycoway/devices/__init__.py +1 -0
- pycoway-1.3.0/src/pycoway/devices/control.py +154 -0
- pycoway-1.3.0/src/pycoway/devices/data.py +194 -0
- pycoway-1.3.0/src/pycoway/devices/models.py +56 -0
- pycoway-1.3.0/src/pycoway/devices/parser.py +197 -0
- pycoway-1.3.0/src/pycoway/enums.py +5 -0
- pycoway-1.3.0/src/pycoway/exceptions.py +29 -0
- pycoway-1.3.0/src/pycoway/py.typed +0 -0
- pycoway-1.3.0/src/pycoway/transport/__init__.py +1 -0
- pycoway-1.3.0/src/pycoway/transport/http.py +177 -0
- pycoway-1.3.0/src/pycoway.egg-info/PKG-INFO +282 -0
- pycoway-1.3.0/src/pycoway.egg-info/SOURCES.txt +29 -0
- pycoway-1.3.0/src/pycoway.egg-info/dependency_links.txt +1 -0
- pycoway-1.3.0/src/pycoway.egg-info/requires.txt +8 -0
- pycoway-1.3.0/src/pycoway.egg-info/top_level.txt +1 -0
- pycoway-1.3.0/tests/test_client.py +66 -0
- pycoway-1.3.0/tests/test_http.py +138 -0
- pycoway-1.3.0/tests/test_models.py +116 -0
- pycoway-1.3.0/tests/test_parser.py +177 -0
pycoway-1.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 RobertD502
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pycoway-1.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycoway
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: An asynchronous Python library for Coway IoCare Air Purifiers
|
|
5
|
+
Author: Antonio112009
|
|
6
|
+
Maintainer: Antonio112009
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/antonio112009/pycoway
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/antonio112009/pycoway/issues
|
|
10
|
+
Project-URL: Source, https://github.com/antonio112009/pycoway/
|
|
11
|
+
Project-URL: Upstream, https://github.com/RobertD502/cowayaio
|
|
12
|
+
Keywords: coway,iocare,iocare api,coway api,airmega
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
24
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
28
|
+
Requires-Dist: aioresponses>=0.7; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# pycoway
|
|
33
|
+
|
|
34
|
+
[](https://github.com/Antonio112009/pycoway/actions/workflows/ci.yml)
|
|
35
|
+
[](https://pypi.org/project/pycoway/)
|
|
36
|
+
[](https://www.python.org/downloads/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://github.com/Antonio112009/pycoway/releases/latest)
|
|
39
|
+
|
|
40
|
+
`pycoway` is an asynchronous Python client for the [Coway IoCare](https://iocare.com/) API. It is designed for AIRMEGA air purifiers and exposes both state retrieval and device control through a typed, `asyncio`-friendly interface.
|
|
41
|
+
|
|
42
|
+
> Based on [RobertD502/cowayaio](https://github.com/RobertD502/cowayaio) with active maintenance, typed models, tests, CI, and automated releases.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- Async API built on [aiohttp](https://docs.aiohttp.org/)
|
|
47
|
+
- Typed dataclass models for purifier state
|
|
48
|
+
- Device control: power, fan speed, light, timers, modes, button lock, and more
|
|
49
|
+
- Air-quality readings: PM2.5, PM10, CO2, VOC, AQI
|
|
50
|
+
- Filter health monitoring: pre-filter, MAX2, and odor filter
|
|
51
|
+
- Automatic token and session management
|
|
52
|
+
- Full test coverage with GitHub Actions CI
|
|
53
|
+
- Automated semantic version bumping, GitHub releases, and PyPI publishing
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- Python 3.11 or newer
|
|
58
|
+
- A Coway IoCare account with at least one registered purifier
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install pycoway
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For local development:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/Antonio112009/pycoway.git
|
|
70
|
+
cd pycoway
|
|
71
|
+
pip install -e ".[dev]"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import asyncio
|
|
78
|
+
|
|
79
|
+
from pycoway import CowayClient
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def main() -> None:
|
|
83
|
+
async with CowayClient("email@example.com", "password") as client:
|
|
84
|
+
await client.login()
|
|
85
|
+
data = await client.async_get_purifiers_data()
|
|
86
|
+
|
|
87
|
+
for device_id, purifier in data.purifiers.items():
|
|
88
|
+
print(f"{purifier.device_attr.name} ({device_id})")
|
|
89
|
+
print(f" Power: {'On' if purifier.is_on else 'Off'}")
|
|
90
|
+
print(f" Fan Speed: {purifier.fan_speed}")
|
|
91
|
+
print(f" PM2.5: {purifier.particulate_matter_2_5}")
|
|
92
|
+
print(f" AQI: {purifier.air_quality_index}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Device Control
|
|
99
|
+
|
|
100
|
+
Every control method accepts the `device_attr` from a `CowayPurifier` instance:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import asyncio
|
|
104
|
+
|
|
105
|
+
from pycoway import CowayClient, LightMode
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def control_first_purifier() -> None:
|
|
109
|
+
async with CowayClient("email@example.com", "password") as client:
|
|
110
|
+
await client.login()
|
|
111
|
+
data = await client.async_get_purifiers_data()
|
|
112
|
+
|
|
113
|
+
purifier = next(iter(data.purifiers.values()))
|
|
114
|
+
attr = purifier.device_attr
|
|
115
|
+
|
|
116
|
+
await client.async_set_power(attr, is_on=True)
|
|
117
|
+
await client.async_set_auto_mode(attr)
|
|
118
|
+
await client.async_set_fan_speed(attr, speed="2")
|
|
119
|
+
await client.async_set_light(attr, light_on=True)
|
|
120
|
+
await client.async_set_light_mode(attr, LightMode.AQI_OFF)
|
|
121
|
+
await client.async_set_timer(attr, time="120")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
asyncio.run(control_first_purifier())
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Available Control Methods
|
|
128
|
+
|
|
129
|
+
| Method | Parameters | Description |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `async_set_power()` | `is_on: bool` | Turn purifier on or off |
|
|
132
|
+
| `async_set_auto_mode()` | — | Switch to auto mode |
|
|
133
|
+
| `async_set_night_mode()` | — | Switch to night mode |
|
|
134
|
+
| `async_set_eco_mode()` | — | Switch to eco mode (AP-1512HHS only) |
|
|
135
|
+
| `async_set_rapid_mode()` | — | Switch to rapid mode (250s only) |
|
|
136
|
+
| `async_set_fan_speed()` | `speed: str` | Set fan speed: `"1"`, `"2"`, or `"3"` |
|
|
137
|
+
| `async_set_light()` | `light_on: bool` | Toggle light on/off (not for 250s) |
|
|
138
|
+
| `async_set_light_mode()` | `light_mode: LightMode` | Set light mode for advanced models |
|
|
139
|
+
| `async_set_timer()` | `time: str` | Off timer in minutes: `"0"`, `"60"`, `"120"`, `"240"`, `"480"` |
|
|
140
|
+
| `async_set_smart_mode_sensitivity()` | `sensitivity: str` | `"1"` sensitive, `"2"` moderate, `"3"` insensitive |
|
|
141
|
+
| `async_set_button_lock()` | `value: str` | `"1"` lock, `"0"` unlock |
|
|
142
|
+
| `async_change_prefilter_setting()` | `value: int` | Wash frequency: `2`, `3`, or `4` weeks |
|
|
143
|
+
|
|
144
|
+
## Data Model
|
|
145
|
+
|
|
146
|
+
`async_get_purifiers_data()` returns a `PurifierData` dataclass containing a `purifiers` dictionary keyed by device ID.
|
|
147
|
+
|
|
148
|
+
Each `CowayPurifier` includes:
|
|
149
|
+
|
|
150
|
+
### Device Identity
|
|
151
|
+
|
|
152
|
+
| Field | Type | Description |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `device_attr` | `DeviceAttributes` | Device ID, model, name, place ID |
|
|
155
|
+
| `mcu_version` | `str \| None` | Firmware version |
|
|
156
|
+
| `network_status` | `bool \| None` | Network connectivity |
|
|
157
|
+
|
|
158
|
+
### Control State
|
|
159
|
+
|
|
160
|
+
| Field | Type | Description |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `is_on` | `bool \| None` | Power state |
|
|
163
|
+
| `auto_mode` | `bool \| None` | Auto mode |
|
|
164
|
+
| `auto_eco_mode` | `bool \| None` | Auto eco mode |
|
|
165
|
+
| `eco_mode` | `bool \| None` | Eco mode |
|
|
166
|
+
| `night_mode` | `bool \| None` | Night mode |
|
|
167
|
+
| `rapid_mode` | `bool \| None` | Rapid mode |
|
|
168
|
+
| `fan_speed` | `int \| None` | Fan speed level |
|
|
169
|
+
| `light_on` | `bool \| None` | Light state |
|
|
170
|
+
| `light_mode` | `int \| None` | Device-specific light mode |
|
|
171
|
+
| `button_lock` | `int \| None` | Button lock state |
|
|
172
|
+
| `smart_mode_sensitivity` | `int \| None` | Smart mode sensitivity level |
|
|
173
|
+
| `timer` | `str \| None` | Configured off timer |
|
|
174
|
+
| `timer_remaining` | `int \| None` | Remaining timer (minutes) |
|
|
175
|
+
|
|
176
|
+
### Air Quality
|
|
177
|
+
|
|
178
|
+
| Field | Type | Description |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `particulate_matter_2_5` | `int \| None` | PM2.5 (μg/m³) |
|
|
181
|
+
| `particulate_matter_10` | `int \| None` | PM10 (μg/m³) |
|
|
182
|
+
| `carbon_dioxide` | `int \| None` | CO₂ (ppm) |
|
|
183
|
+
| `volatile_organic_compounds` | `int \| None` | VOC level |
|
|
184
|
+
| `air_quality_index` | `int \| None` | AQI value |
|
|
185
|
+
| `aq_grade` | `int \| None` | Air quality grade |
|
|
186
|
+
| `lux_sensor` | `int \| None` | Ambient light sensor |
|
|
187
|
+
|
|
188
|
+
### Filter Health
|
|
189
|
+
|
|
190
|
+
| Field | Type | Description |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `pre_filter_pct` | `int \| None` | Pre-filter remaining (%) |
|
|
193
|
+
| `pre_filter_change_frequency` | `int \| None` | Wash frequency (weeks) |
|
|
194
|
+
| `max2_pct` | `int \| None` | MAX2 filter remaining (%) |
|
|
195
|
+
| `odor_filter_pct` | `int \| None` | Odor filter remaining (%) |
|
|
196
|
+
|
|
197
|
+
For the complete schema, see [`src/pycoway/devices/models.py`](src/pycoway/devices/models.py).
|
|
198
|
+
|
|
199
|
+
## Exceptions
|
|
200
|
+
|
|
201
|
+
All exceptions inherit from `CowayError`:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from pycoway import AuthError, CowayError, PasswordExpired
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Exception | Description |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `CowayError` | Base exception for all library errors |
|
|
210
|
+
| `AuthError` | Authentication failed |
|
|
211
|
+
| `PasswordExpired` | Coway requires a password change |
|
|
212
|
+
| `ServerMaintenance` | Coway API is under maintenance |
|
|
213
|
+
| `RateLimited` | Coway temporarily blocked the account |
|
|
214
|
+
| `NoPlaces` | No places configured in the IoCare account |
|
|
215
|
+
| `NoPurifiers` | No air purifiers found |
|
|
216
|
+
|
|
217
|
+
## Migrating from cowayaio
|
|
218
|
+
|
|
219
|
+
If you're switching from the original `cowayaio` package:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip uninstall cowayaio
|
|
223
|
+
pip install pycoway
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Update your imports:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# Before
|
|
230
|
+
from cowayaio import CowayClient
|
|
231
|
+
|
|
232
|
+
# After
|
|
233
|
+
from pycoway import CowayClient
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
git clone https://github.com/Antonio112009/pycoway.git
|
|
240
|
+
cd pycoway
|
|
241
|
+
pip install -e ".[dev]"
|
|
242
|
+
pytest
|
|
243
|
+
ruff check .
|
|
244
|
+
ruff format --check .
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Feature work should branch from `development`, and pull requests merge into `development` first. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.
|
|
248
|
+
|
|
249
|
+
## Release Flow
|
|
250
|
+
|
|
251
|
+
- PRs from `development` to `main` trigger the release workflow when merged
|
|
252
|
+
- The workflow bumps `src/pycoway/__version__.py`
|
|
253
|
+
- PRs to `main` must have exactly one version label: `patch`, `minor`, or `major`
|
|
254
|
+
- A git tag and GitHub release are created automatically
|
|
255
|
+
- The package is published to PyPI automatically
|
|
256
|
+
|
|
257
|
+
## Project Structure
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
src/pycoway/
|
|
261
|
+
├── __init__.py # Public API exports
|
|
262
|
+
├── __version__.py # Version string
|
|
263
|
+
├── client.py # Public CowayClient entry point
|
|
264
|
+
├── constants.py # API constants
|
|
265
|
+
├── enums.py # Enumerations
|
|
266
|
+
├── exceptions.py # Public exception hierarchy
|
|
267
|
+
├── py.typed # PEP 561 marker
|
|
268
|
+
├── account/
|
|
269
|
+
│ ├── auth.py # Authentication (login, token refresh)
|
|
270
|
+
│ └── maintenance.py # Server maintenance checks
|
|
271
|
+
├── devices/
|
|
272
|
+
│ ├── control.py # Purifier control commands
|
|
273
|
+
│ ├── data.py # Data fetching (purifiers, filters, air quality)
|
|
274
|
+
│ ├── models.py # Dataclasses (CowayPurifier, PurifierData)
|
|
275
|
+
│ └── parser.py # HTML/JSON response parsing
|
|
276
|
+
└── transport/
|
|
277
|
+
└── http.py # HTTP base client with session management
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
[MIT](LICENSE), originally authored by [RobertD502](https://github.com/RobertD502)
|
pycoway-1.3.0/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# pycoway
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Antonio112009/pycoway/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/pycoway/)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/Antonio112009/pycoway/releases/latest)
|
|
8
|
+
|
|
9
|
+
`pycoway` is an asynchronous Python client for the [Coway IoCare](https://iocare.com/) API. It is designed for AIRMEGA air purifiers and exposes both state retrieval and device control through a typed, `asyncio`-friendly interface.
|
|
10
|
+
|
|
11
|
+
> Based on [RobertD502/cowayaio](https://github.com/RobertD502/cowayaio) with active maintenance, typed models, tests, CI, and automated releases.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Async API built on [aiohttp](https://docs.aiohttp.org/)
|
|
16
|
+
- Typed dataclass models for purifier state
|
|
17
|
+
- Device control: power, fan speed, light, timers, modes, button lock, and more
|
|
18
|
+
- Air-quality readings: PM2.5, PM10, CO2, VOC, AQI
|
|
19
|
+
- Filter health monitoring: pre-filter, MAX2, and odor filter
|
|
20
|
+
- Automatic token and session management
|
|
21
|
+
- Full test coverage with GitHub Actions CI
|
|
22
|
+
- Automated semantic version bumping, GitHub releases, and PyPI publishing
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.11 or newer
|
|
27
|
+
- A Coway IoCare account with at least one registered purifier
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pycoway
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For local development:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/Antonio112009/pycoway.git
|
|
39
|
+
cd pycoway
|
|
40
|
+
pip install -e ".[dev]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import asyncio
|
|
47
|
+
|
|
48
|
+
from pycoway import CowayClient
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def main() -> None:
|
|
52
|
+
async with CowayClient("email@example.com", "password") as client:
|
|
53
|
+
await client.login()
|
|
54
|
+
data = await client.async_get_purifiers_data()
|
|
55
|
+
|
|
56
|
+
for device_id, purifier in data.purifiers.items():
|
|
57
|
+
print(f"{purifier.device_attr.name} ({device_id})")
|
|
58
|
+
print(f" Power: {'On' if purifier.is_on else 'Off'}")
|
|
59
|
+
print(f" Fan Speed: {purifier.fan_speed}")
|
|
60
|
+
print(f" PM2.5: {purifier.particulate_matter_2_5}")
|
|
61
|
+
print(f" AQI: {purifier.air_quality_index}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Device Control
|
|
68
|
+
|
|
69
|
+
Every control method accepts the `device_attr` from a `CowayPurifier` instance:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
|
|
74
|
+
from pycoway import CowayClient, LightMode
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def control_first_purifier() -> None:
|
|
78
|
+
async with CowayClient("email@example.com", "password") as client:
|
|
79
|
+
await client.login()
|
|
80
|
+
data = await client.async_get_purifiers_data()
|
|
81
|
+
|
|
82
|
+
purifier = next(iter(data.purifiers.values()))
|
|
83
|
+
attr = purifier.device_attr
|
|
84
|
+
|
|
85
|
+
await client.async_set_power(attr, is_on=True)
|
|
86
|
+
await client.async_set_auto_mode(attr)
|
|
87
|
+
await client.async_set_fan_speed(attr, speed="2")
|
|
88
|
+
await client.async_set_light(attr, light_on=True)
|
|
89
|
+
await client.async_set_light_mode(attr, LightMode.AQI_OFF)
|
|
90
|
+
await client.async_set_timer(attr, time="120")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
asyncio.run(control_first_purifier())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Available Control Methods
|
|
97
|
+
|
|
98
|
+
| Method | Parameters | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `async_set_power()` | `is_on: bool` | Turn purifier on or off |
|
|
101
|
+
| `async_set_auto_mode()` | — | Switch to auto mode |
|
|
102
|
+
| `async_set_night_mode()` | — | Switch to night mode |
|
|
103
|
+
| `async_set_eco_mode()` | — | Switch to eco mode (AP-1512HHS only) |
|
|
104
|
+
| `async_set_rapid_mode()` | — | Switch to rapid mode (250s only) |
|
|
105
|
+
| `async_set_fan_speed()` | `speed: str` | Set fan speed: `"1"`, `"2"`, or `"3"` |
|
|
106
|
+
| `async_set_light()` | `light_on: bool` | Toggle light on/off (not for 250s) |
|
|
107
|
+
| `async_set_light_mode()` | `light_mode: LightMode` | Set light mode for advanced models |
|
|
108
|
+
| `async_set_timer()` | `time: str` | Off timer in minutes: `"0"`, `"60"`, `"120"`, `"240"`, `"480"` |
|
|
109
|
+
| `async_set_smart_mode_sensitivity()` | `sensitivity: str` | `"1"` sensitive, `"2"` moderate, `"3"` insensitive |
|
|
110
|
+
| `async_set_button_lock()` | `value: str` | `"1"` lock, `"0"` unlock |
|
|
111
|
+
| `async_change_prefilter_setting()` | `value: int` | Wash frequency: `2`, `3`, or `4` weeks |
|
|
112
|
+
|
|
113
|
+
## Data Model
|
|
114
|
+
|
|
115
|
+
`async_get_purifiers_data()` returns a `PurifierData` dataclass containing a `purifiers` dictionary keyed by device ID.
|
|
116
|
+
|
|
117
|
+
Each `CowayPurifier` includes:
|
|
118
|
+
|
|
119
|
+
### Device Identity
|
|
120
|
+
|
|
121
|
+
| Field | Type | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `device_attr` | `DeviceAttributes` | Device ID, model, name, place ID |
|
|
124
|
+
| `mcu_version` | `str \| None` | Firmware version |
|
|
125
|
+
| `network_status` | `bool \| None` | Network connectivity |
|
|
126
|
+
|
|
127
|
+
### Control State
|
|
128
|
+
|
|
129
|
+
| Field | Type | Description |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `is_on` | `bool \| None` | Power state |
|
|
132
|
+
| `auto_mode` | `bool \| None` | Auto mode |
|
|
133
|
+
| `auto_eco_mode` | `bool \| None` | Auto eco mode |
|
|
134
|
+
| `eco_mode` | `bool \| None` | Eco mode |
|
|
135
|
+
| `night_mode` | `bool \| None` | Night mode |
|
|
136
|
+
| `rapid_mode` | `bool \| None` | Rapid mode |
|
|
137
|
+
| `fan_speed` | `int \| None` | Fan speed level |
|
|
138
|
+
| `light_on` | `bool \| None` | Light state |
|
|
139
|
+
| `light_mode` | `int \| None` | Device-specific light mode |
|
|
140
|
+
| `button_lock` | `int \| None` | Button lock state |
|
|
141
|
+
| `smart_mode_sensitivity` | `int \| None` | Smart mode sensitivity level |
|
|
142
|
+
| `timer` | `str \| None` | Configured off timer |
|
|
143
|
+
| `timer_remaining` | `int \| None` | Remaining timer (minutes) |
|
|
144
|
+
|
|
145
|
+
### Air Quality
|
|
146
|
+
|
|
147
|
+
| Field | Type | Description |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `particulate_matter_2_5` | `int \| None` | PM2.5 (μg/m³) |
|
|
150
|
+
| `particulate_matter_10` | `int \| None` | PM10 (μg/m³) |
|
|
151
|
+
| `carbon_dioxide` | `int \| None` | CO₂ (ppm) |
|
|
152
|
+
| `volatile_organic_compounds` | `int \| None` | VOC level |
|
|
153
|
+
| `air_quality_index` | `int \| None` | AQI value |
|
|
154
|
+
| `aq_grade` | `int \| None` | Air quality grade |
|
|
155
|
+
| `lux_sensor` | `int \| None` | Ambient light sensor |
|
|
156
|
+
|
|
157
|
+
### Filter Health
|
|
158
|
+
|
|
159
|
+
| Field | Type | Description |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `pre_filter_pct` | `int \| None` | Pre-filter remaining (%) |
|
|
162
|
+
| `pre_filter_change_frequency` | `int \| None` | Wash frequency (weeks) |
|
|
163
|
+
| `max2_pct` | `int \| None` | MAX2 filter remaining (%) |
|
|
164
|
+
| `odor_filter_pct` | `int \| None` | Odor filter remaining (%) |
|
|
165
|
+
|
|
166
|
+
For the complete schema, see [`src/pycoway/devices/models.py`](src/pycoway/devices/models.py).
|
|
167
|
+
|
|
168
|
+
## Exceptions
|
|
169
|
+
|
|
170
|
+
All exceptions inherit from `CowayError`:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from pycoway import AuthError, CowayError, PasswordExpired
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Exception | Description |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `CowayError` | Base exception for all library errors |
|
|
179
|
+
| `AuthError` | Authentication failed |
|
|
180
|
+
| `PasswordExpired` | Coway requires a password change |
|
|
181
|
+
| `ServerMaintenance` | Coway API is under maintenance |
|
|
182
|
+
| `RateLimited` | Coway temporarily blocked the account |
|
|
183
|
+
| `NoPlaces` | No places configured in the IoCare account |
|
|
184
|
+
| `NoPurifiers` | No air purifiers found |
|
|
185
|
+
|
|
186
|
+
## Migrating from cowayaio
|
|
187
|
+
|
|
188
|
+
If you're switching from the original `cowayaio` package:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
pip uninstall cowayaio
|
|
192
|
+
pip install pycoway
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Update your imports:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
# Before
|
|
199
|
+
from cowayaio import CowayClient
|
|
200
|
+
|
|
201
|
+
# After
|
|
202
|
+
from pycoway import CowayClient
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/Antonio112009/pycoway.git
|
|
209
|
+
cd pycoway
|
|
210
|
+
pip install -e ".[dev]"
|
|
211
|
+
pytest
|
|
212
|
+
ruff check .
|
|
213
|
+
ruff format --check .
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Feature work should branch from `development`, and pull requests merge into `development` first. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.
|
|
217
|
+
|
|
218
|
+
## Release Flow
|
|
219
|
+
|
|
220
|
+
- PRs from `development` to `main` trigger the release workflow when merged
|
|
221
|
+
- The workflow bumps `src/pycoway/__version__.py`
|
|
222
|
+
- PRs to `main` must have exactly one version label: `patch`, `minor`, or `major`
|
|
223
|
+
- A git tag and GitHub release are created automatically
|
|
224
|
+
- The package is published to PyPI automatically
|
|
225
|
+
|
|
226
|
+
## Project Structure
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
src/pycoway/
|
|
230
|
+
├── __init__.py # Public API exports
|
|
231
|
+
├── __version__.py # Version string
|
|
232
|
+
├── client.py # Public CowayClient entry point
|
|
233
|
+
├── constants.py # API constants
|
|
234
|
+
├── enums.py # Enumerations
|
|
235
|
+
├── exceptions.py # Public exception hierarchy
|
|
236
|
+
├── py.typed # PEP 561 marker
|
|
237
|
+
├── account/
|
|
238
|
+
│ ├── auth.py # Authentication (login, token refresh)
|
|
239
|
+
│ └── maintenance.py # Server maintenance checks
|
|
240
|
+
├── devices/
|
|
241
|
+
│ ├── control.py # Purifier control commands
|
|
242
|
+
│ ├── data.py # Data fetching (purifiers, filters, air quality)
|
|
243
|
+
│ ├── models.py # Dataclasses (CowayPurifier, PurifierData)
|
|
244
|
+
│ └── parser.py # HTML/JSON response parsing
|
|
245
|
+
└── transport/
|
|
246
|
+
└── http.py # HTTP base client with session management
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
[MIT](LICENSE), originally authored by [RobertD502](https://github.com/RobertD502)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pycoway"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "An asynchronous Python library for Coway IoCare Air Purifiers"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Antonio112009" },
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{ name = "Antonio112009" },
|
|
17
|
+
]
|
|
18
|
+
keywords = ["coway", "iocare", "iocare api", "coway api", "airmega"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"aiohttp>=3.9.0",
|
|
30
|
+
"beautifulsoup4>=4.12.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=8.0",
|
|
36
|
+
"pytest-asyncio>=0.24",
|
|
37
|
+
"aioresponses>=0.7",
|
|
38
|
+
"ruff",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/antonio112009/pycoway"
|
|
43
|
+
"Bug Reports" = "https://github.com/antonio112009/pycoway/issues"
|
|
44
|
+
Source = "https://github.com/antonio112009/pycoway/"
|
|
45
|
+
Upstream = "https://github.com/RobertD502/cowayaio"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.dynamic]
|
|
48
|
+
version = { attr = "pycoway.__version__.__version__" }
|
|
49
|
+
|
|
50
|
+
[tool.setuptools]
|
|
51
|
+
package-dir = {"" = "src"}
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["src"]
|
|
55
|
+
include = ["pycoway*"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.package-data]
|
|
58
|
+
pycoway = ["py.typed"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
target-version = "py311"
|
|
62
|
+
line-length = 100
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = [
|
|
66
|
+
"E", # pycodestyle errors
|
|
67
|
+
"W", # pycodestyle warnings
|
|
68
|
+
"F", # pyflakes
|
|
69
|
+
"I", # isort
|
|
70
|
+
"UP", # pyupgrade
|
|
71
|
+
"B", # flake8-bugbear
|
|
72
|
+
"SIM", # flake8-simplify
|
|
73
|
+
"TCH", # flake8-type-checking
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[tool.pytest.ini_options]
|
|
77
|
+
asyncio_mode = "auto"
|
|
78
|
+
testpaths = ["tests"]
|
pycoway-1.3.0/setup.cfg
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Init file for pycoway."""
|
|
2
|
+
|
|
3
|
+
from .__version__ import __version__
|
|
4
|
+
from .client import CowayClient
|
|
5
|
+
from .constants import LightMode
|
|
6
|
+
from .devices.models import CowayPurifier, DeviceAttributes, PurifierData
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
AuthError,
|
|
9
|
+
CowayError,
|
|
10
|
+
NoPlaces,
|
|
11
|
+
NoPurifiers,
|
|
12
|
+
PasswordExpired,
|
|
13
|
+
RateLimited,
|
|
14
|
+
ServerMaintenance,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AuthError",
|
|
19
|
+
"CowayClient",
|
|
20
|
+
"CowayError",
|
|
21
|
+
"CowayPurifier",
|
|
22
|
+
"DeviceAttributes",
|
|
23
|
+
"LightMode",
|
|
24
|
+
"NoPlaces",
|
|
25
|
+
"NoPurifiers",
|
|
26
|
+
"PasswordExpired",
|
|
27
|
+
"PurifierData",
|
|
28
|
+
"RateLimited",
|
|
29
|
+
"ServerMaintenance",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Account and authentication functionality for pycoway."""
|