Repository navigation
Conversation
Owner
|
@arypien Thank you for the PR. Did you capture packets? I will need full packet captures with all of the available states for future maintenance and debugging. Feel free to reach out on discord: homefire1608. This should be based on https://github.com/webdjoe/pyvesync/tree/air-fryer-refactor. I refactored the air fryer architecture. I can refactor your PR on top once you send the captures. |
webdjoe
force-pushed
the
feat/caf-dc111s-aeu
branch
from
November 2, 2026 02:42
a89d8cc to
04a54fd
Compare
6 of 13 tasks
8 of 13 tasks
webdjoe
added a commit
that referenced
this pull request
Nov 5, 2026
Add the Cosori Turbo Tower Pro Smart Air Fryer (CAF-DC111S-AEU) as a dual-chamber VeSyncDualAirFryer with its seven cook modes and per-mode temperature limits from the APK. - Map the DC111S `hold` cook status (Sync cook wait) to AirFryerCookStatus.HOLD. The wait counts down only while the other chamber cooks. - Add the APK-backed DC111S Sync error codes 11914000, 11915000 and 11926000 to ErrorCodes. - Tests from the PR #536 captures; docs, README and supported devices. Hardware testing (model + firmware) must be done by a human with the device. Co-authored-by: arypien <andrzejrypien@proton.me>
Owner
webdjoe
added a commit
that referenced
this pull request
Nov 5, 2026
## Description This PR rewrites air fryer support around a single `VeSyncFryer` interface and adds five new Cosori models. It is the integration branch for the air fryer work. Each part was reviewed and squash-merged here as its own PR: | PR | Change | | --- | --- | | #517 | Add the Dual Blaze (CAF-P583S) | | #575 | Report preheat on the TurboBlaze, and send `readyStart` false (from #560) | | #578 | Skip devices that fail to initialize, and log unexpected update errors | | #579 | Add the chamber interface, presets and temperature limits from the device map, and unit handling | | #580 | Make the fryers resilient to failed or unreadable responses, and keep local state consistent | | #581 | Add the CAF-DC111S-AEU Turbo Tower Pro (replaces #536, from arypien's captures) | | #582 | Keep the API code and message for unknown error codes (merged to `dev`, then cherry-picked here) | ### Supported fryers | Model | Class | Chambers | Preheat | Pause / resume | | --- | --- | --- | --- | --- | | CS137-AF, CS158-AF | `VeSyncAirFryer158` | 1 | ✔ | ✔ | | CAF-DC601S TurboBlaze (new) | `VeSyncTurboBlazeFryer` | 1 | ✔ | | | CAF-P583S Dual Blaze, KUS and KEU (new) | `VeSyncDualBlazeFryer` | 1 | | | | CAF-TF101S Dual Air Fryer (new) | `VeSyncDualAirFryer` | 1, 2, whole, both | | | | CAF-TF102S (new) | `VeSyncDualAirFryer` | 1, 2, whole, both | | | | CAF-DC111S-AEU Turbo Tower Pro (new) | `VeSyncDualAirFryer` | 1 (upper), 2 (lower), both | | | ### Common interface These are on `VeSyncFryer` (`base_devices/fryer_base.py`): - **Cook controls.** Every fryer has: - `set_mode(cook_time, cook_temp, *, temp_unit, preheat_time, cook_mode, chamber)`; - `set_mode_from_recipe(recipe, chamber)`; - `end(chamber)` and `update()`. `stop()`, `resume()` and `cook_from_preheat()` are implemented only on the CS137/CS158. On the other fryers they log a message and return `False` without sending a request. - **Chambers.** `AirFryerChamber` has `CHAMBER_1`, `CHAMBER_2`, `WHOLE` and `BOTH`. The device also has: - `supported_chambers` and `is_dual_chamber`; - `get_chamber_state()`; - `sync_chambers` and `work_chamber`. `BOTH` is sent as the app's Sync mode. Each chamber has its own `FryerState`. - **Cook modes and presets come from the device map.** Each model's map entry defines its cook modes (`AirFryerModeConfig`) and per-mode temperature limits and steps (`AirFryerTempLimits`). The fryer exposes them through `get_cook_mode()`, `get_mode_recipe()` and `temp_limits()`. A temperature is rounded to the mode's step, then checked against the mode's range. - **Temperature units.** The device map gives each model its starting unit and step, and the unit the API reports takes over once the fryer updates. Times are in seconds. Temperatures are in the fryer's unit, unless you pass `temp_unit`. - **State.** `FryerState` tracks: - the cook status, as the `AirFryerCookStatus` enum; - the preheat, cook and total remaining times, which count down locally between updates and carry across stop, resume and preheat; - the set time and temperature, and the cook mode. ### Error handling - A command the fryer refuses returns `False`, leaves the state as it was, and records the reason in `last_response`. - A failed or unreadable update also leaves the state unchanged. That covers no response, an error code, a null result, an unknown status, or an out-of-range step index. - Air fryer error codes are added to `ErrorCodes`, including the DC111S Sync refusals 11914000, 11915000 and 11926000. - An unknown error code is looked up the way the VeSync app does it: the code is truncated to the thousand, and if that doesn't match, it falls back to `UNKNOWN`. Either way the API code and message are kept for `last_response` and the logs. ### Other changes - `DeviceContainer` skips a device whose class raises `VeSyncError` while it is created, with a warning. One bad device no longer stops discovery of the rest. - `update_all_devices()` logs errors other than `VeSyncError` with their traceback, instead of dropping them. - `VeSync.save_credentials(None)` now saves to the configured token file, or to `.vesync_auth` in the working directory if none is set. It used to save nothing. Its docstring still says "If None, no action is taken". - Ruff is pinned to 0.15.21 in `pyproject.toml`, `requirements-dev.txt` and the pre-commit config, and `ERA001` (commented-out code) is now enforced. - Docs: - `docs/devices/kitchen.md` is rewritten; - the README and `docs/supported_devices.md` list the new models; - the `AGENTS.md` and test guides describe the air fryer tests. ### Breaking changes for CS137/CS158 users (released in 3.4.2) | 3.4.2 | This PR | | --- | --- | | `cook(set_temp, set_time)` (minutes) | `set_mode(cook_time, cook_temp)` (seconds) | | `set_preheat(target_temp, cook_time)` | `set_mode(cook_time, cook_temp, preheat_time=...)` | | `pause()` | `stop()` | | `check_status()` | `update()` | | `state.is_resumable` | `state.can_resume` | | `state.remaining_time` | `state.total_time_remaining` | | `state.is_heating` | `state.is_preheating` | | `state.cook_status` as a string | `AirFryerCookStatus` | `toggle_switch()` is deprecated and sends no request, because air fryers have no on/off command. ## Type of change - [ ] Bug fix (`fix:`) - [x] New feature or device support (`feat:`) - [ ] Documentation (`docs:`) - [ ] Refactor / test / chore (no functional change) ## Checklist - [ ] This PR is a **single** feature, fix, or edit. It is an integration branch: each part was reviewed and merged as its own PR (see the table above). - [x] The change follows the intended architecture (base class → device + API mixin → device map; state in `DeviceState`; models in `models/`; constants in `pyvesync.const`). - [x] No API request/response fields were invented. All come from real packet captures, and the cook modes and limits come from the VeSync app. The exceptions are flagged under hardware verification below. - [x] `ruff check`, `pylint`, `mypy` and `pytest` pass locally: 613 passed, 3 skipped; pylint 10/10. - [x] Tests are added and the API fixtures are written. The YAML fixtures under `src/tests/api/vesynckitchen/` cover `update()` for each fryer. `test_fryer_captures.py`, `test_fryer_chambers.py`, `test_fryer_units.py`, `test_fryer_resilience.py`, `test_errors.py` and `test_manager_resilience.py` cover the rest. - [x] Documentation is updated. ## Real-hardware verification - [ ] Tested on the physical device. - CAF-P583S-KUS, firmware v1.0.15: remote start and stop, status polling, and all 11 presets (#517). - CS137-AF / CS158-AF: model and firmware `____` - CAF-DC601S: model and firmware `____` - CAF-TF101S: model and firmware `____` - [ ] Not applicable. - [ ] I do not own the device; a packet capture is provided / the device has been shared with a maintainer for verification. Still pending hardware verification: - **CAF-TF102S:** mapped from the VeSync app. There is no capture. - **CAF-DC111S-AEU:** the status captures come from #536. The cook and end requests have not been tested on the device. - **CAF-P583S-KEU:** there is no capture. It is assumed to match the KUS, apart from using Celsius. - **CAF-TF101S:** there is no capture with `readyStart` false. Its status after a cook starts (`COOKING`) is inferred from the CS158 captures. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Red Hodgerson <1190796+RedsGT@users.noreply.github.com> Co-authored-by: Darren Griffin <darren.griffin@live.co.uk> Co-authored-by: ScribleSean <56331494+ScribleSean@users.noreply.github.com> Co-authored-by: arypien <andrzejrypien@proton.me>
5 of 13 tasks
webdjoe
added a commit
that referenced
this pull request
Nov 5, 2026
## Description Releases pyvesync 3.5.0 from `dev` to `master`. Merging publishes to PyPI and deploys the docs. **Main changes:** air fryer support is rewritten around a single `VeSyncFryer` interface, with new Cosori models added. New devices and fixes for outlets, humidifiers and the thermostat are also included. **CS137/CS158 air fryer users have breaking changes, listed below.** ### New features and devices - **Air fryers (#503):** a common interface for cooking, chambers, presets, temperature limits and units. - New models: CAF-DC601S TurboBlaze, CAF-P583S Dual Blaze (KUS and KEU), CAF-TF101S, CAF-TF102S and CAF-DC111S-AEU Turbo Tower Pro. - A failed or unreadable response now leaves the fryer's state unchanged, and a refused command returns `False`. - **WYLDR16A1081 smart plug (#508):** uses `VeSyncBSDOGPlug` without energy history. - This adds `OutletFeatures.ENERGY_HISTORY` and `supports_energy_history`. Weekly, monthly and yearly energy requests now do nothing on outlets without the flag. - **RGB nightlight for LUH-O451S-WEU (#502):** adds `set_rgb_nightlight()` and `supports_rgb_nightlight`. ### Breaking changes For CS137/CS158 air fryers: | 3.4.2 | 3.5.0 | | --- | --- | | `cook(set_temp, set_time)` (minutes) | `set_mode(cook_time, cook_temp)` (seconds) | | `set_preheat(target_temp, cook_time)` | `set_mode(cook_time, cook_temp, preheat_time=...)` | | `pause()` | `stop()` | | `check_status()` | `update()` | | `state.is_resumable` | `state.can_resume` | | `state.remaining_time` | `state.total_time_remaining` | | `state.is_heating` | `state.is_preheating` | | `state.cook_status` as a string | `AirFryerCookStatus` | - `AirFryer158138State` is removed; use `FryerState`. - The device map `setup_entry` changes from `CS137-AF/CS158-AF` to `CS158-AF`. - `toggle_switch()` on air fryers is deprecated and sends no request, because they have no on/off command. Other changes that may affect you: - The minimum `aiohttp` version rises from 3.8.1 to 3.14.4 (#573), because older releases have published security advisories. - `VeSync.save_credentials(None)` now saves to the configured token file, or to `.vesync_auth` in the working directory if none is set. It used to do nothing (#503). ### Fixes - **Sprout Humidifier LEH-B381S:** `set_nightlight_brightness()` and `toggle_nightlight()` now call the API instead of the base-class stub, and toggling no longer sends a null brightness (#528). - **Aura thermostat:** details processing read `supportMode` from the dataclass instead of the response instance (#555). - **Unknown API error codes:** `last_response` and logs now keep the API's code and message (#582). - **Device discovery:** a device whose class raises `VeSyncError` during setup is skipped with a warning, so it no longer stops discovery of the others (#503). - **`update_all_devices()`:** errors other than `VeSyncError` are now logged with a traceback instead of being dropped (#503). - **Test suite:** the event loop is closed in teardown, preventing a file descriptor leak (#514). ### Docs, tooling and CI - **Contribution docs (#572):** - Adds `AGENTS.md`, a PR template and issue forms. - Rewrites the contribution guides. - Refreshes the README and docs. - **Python and mypy (#574):** - CI and tox now test Python 3.14. - mypy targets Python 3.11. - Removes the unused `python-dateutil` from `requirements.txt`. - **Ruff and pre-commit:** - `ruff` is pinned to 0.15.21 in `pyproject.toml` and `requirements-dev.txt`, and `ERA001` is enforced (#503). - The pre-commit hooks move to mypy v2.4.1 and ruff v0.17.3 (#498). - **GitHub Actions:** `actions/checkout` moves to v7 (#527) and `actions/setup-python` to v7 (#534). ### Known issues - **RGB nightlight:** the API can return stale values for several minutes after a change, and the timeout added to handle this isn't fully reliable. Minimum brightness is 40% (#502). - **`save_credentials()` docstring:** it still says "If None, no action is taken", which is no longer true. - **Ruff versions:** the pre-commit hook (v0.17.3) doesn't match the pinned version (0.15.21). ## Type of change - [ ] Bug fix (`fix:`) - [ ] New feature or device support (`feat:`) - [ ] Documentation (`docs:`) - [x] Refactor / test / chore (no functional change): release PR. The functional changes were reviewed in the PRs linked above. ## Checklist - [ ] This PR is a **single** feature, fix, or edit. This is a release PR, so it combines the PRs above. - [x] The change follows the intended architecture (base class → device + API mixin → device map; state in `DeviceState`; models in `models/`; constants in `pyvesync.const`). - [ ] No API request/response fields were invented. Not checked because some models have no capture: see "Mapped or inferred without a capture" below. - [x] `ruff check`, `pylint`, `mypy`, and `pytest` pass locally: 613 passed, 3 skipped on Python 3.12; pylint 10/10. - [x] Tests added/updated and API fixtures written (`pytest --write_api`) where relevant. - [x] Documentation updated if behavior or public API changed. ## Real-hardware verification - [ ] Tested on the physical device. Partial; status by model: | Model | Status | Firmware | Source | | --- | --- | --- | --- | | CAF-P583S-KUS | Tested: remote start and stop, status polling, 11 presets | v1.0.15 | #517, #503 | | LTM-A401S-WUS | Tested | 1.0.60 | #555 | | LUH-O451S-WEU | Tested | `____` | #502 | | LEH-B381S | Tested by the reporter | `____` | #528 | | WYLDR16A1081 | Not stated in the PR | `____` | #508 | | CS137-AF / CS158-AF | Not recorded | `____` | #503 | | CAF-DC601S | Not recorded | `____` | #503 | | CAF-TF101S | Not recorded | `____` | #503 | | CAF-TF102S | Pending | `____` | #503 | | CAF-DC111S-AEU | Pending | `____` | #503, #581 | | CAF-P583S-KEU | Pending | `____` | #503 | Mapped or inferred without a capture: - **CAF-TF102S:** mapped from the VeSync app; there is no capture. - **CAF-DC111S-AEU:** status captures come from #536. Cook and end requests have not been tested on the device. - **CAF-P583S-KEU:** there is no capture. It is assumed to match the KUS, apart from using Celsius. - **CAF-TF101S:** there is no capture with `readyStart` false, so its `COOKING` status after a cook starts is inferred from the CS158 captures. - [ ] Not applicable (explain why): `____` - [ ] I do not own the device; a packet capture is provided / the device has been shared with a maintainer for verification.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add support for the Cosori CAF-DC111S-AEU Turbo Tower Pro Smart Air Fryer
This PR adds support for the European Cosori CAF-DC111S-AEU Turbo Tower Pro Smart Air Fryer.
Device information
CAF-DC111S-AEUSKAVS_WFON_CAF-DC111S-AEU_EUAPI
Unlike previous Cosori air fryers, this model communicates through the new:
The following methods have been implemented:
getAirfryerMultiStatusstartMultiCookendCookFeatures
startMultiCookprepares the selected program, but the appliance still requires physical confirmation by pressing the Start button on the device.Testing
The implementation was developed and validated using a real European CAF-DC111S-AEU device.
The API was reverse-engineered by observing the official VeSync application and verifying every implemented request against the physical device.
The PR also includes unit tests covering:
11923000)All added unit tests pass successfully.
Development note
AI-assisted tooling (ChatGPT) was used during development to help analyze the protocol, explore the codebase and draft parts of the implementation. All code, API behaviour and functionality were manually reviewed, tested and validated on a real device before submission.