Skip to content

feat: add Cosori Dual Blaze (CAF-P583S) air fryer support#517

Open
RedsGT wants to merge 6 commits into
webdjoe:air-fryer-refactorfrom
RedsGT:wfon-caf-p583s
Open

feat: add Cosori Dual Blaze (CAF-P583S) air fryer support#517
RedsGT wants to merge 6 commits into
webdjoe:air-fryer-refactorfrom
RedsGT:wfon-caf-p583s

Conversation

@RedsGT
Copy link
Copy Markdown

@RedsGT RedsGT commented May 10, 2026

Adds support for the Cosori Dual Blaze 6.8qt smart air fryer
(CAF-P583S-KUS / CAF-P583S-KEU). Resolves the request in #477.

The Dual Blaze is a single-chamber model with dual heating elements
that uses the same bypassV2 protocol as TurboBlaze (startCook,
endCook, getAirfryerStatus), so it can reuse VeSyncTurboBlazeFryer
without needing a new device class.

Commits

  1. Register CAF-P583S in device_map.py (basic support, default
    AirFry preset).
  2. Add 10 Dual Blaze presets + 5 cook modes — recipe IDs from
    packet captures of the official VeSync app, default temp/time per
    the CAF-P583S-KUS user manual.

Tested on

  • CAF-P583S-KUS, firmware v1.0.15
  • Verified: remote start, remote stop, status polling (cookStatus,
    currentTemp, cookSetTemp, totalTimeRemaining, stepArray), all 11
    presets accepted by the device

Notes

  • The Dual Blaze has no preheat function.
  • Temperature range per manual: 175–400°F / 80–205°C.

🤖 The patches in this PR were authored with assistance from Claude
(Anthropic).

RedsGT and others added 2 commits May 10, 2026 10:53
Registers CAF-P583S-KUS and CAF-P583S-KEU using the existing
VeSyncTurboBlazeFryer class -- same bypassV2 protocol (startCook /
endCook / getAirfryerStatus). Enables remote start, stop, and status
polling out of the box. Default AirFry preset; the device's other 10
presets (Broil, Roast, Bake, Reheat, Steak, Seafood, Veggies, French
Fries, Frozen, Chicken) are added in a follow-up commit.

Notes:
- Dual Blaze has no preheat function (only RESUMABLE feature flag set)
- Temperature range 175-400F per the manual
- Status map adds standby/preheating/keeping which the device emits

🤖 This commit was authored with assistance from Claude (Anthropic).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the full set of cooking presets exposed by the VeSync app for the
Cosori Dual Blaze (CAF-P583S):

  Recipe IDs from packet captures of the official VeSync app:
    Air Fry      14   Broil        17   Roast        13
    Bake          9   Reheat       16   Steak         1
    Seafood       3   Veggies      15   French Fries  6
    Frozen        5   Chicken       2

Default temperature/time per the CAF-P583S-KUS user manual.

New AirFryerCookModes enum values:
  STEAK, SEAFOOD, VEGGIES, FRENCH_FRIES, CHICKEN

The CAF-P583S device_map entry now wires up all 11 modes via cook_modes.
Air Fry (id 14) was already defined in AirFryerPresets and is reused as
the default preset.

🤖 This commit was authored with assistance from Claude (Anthropic).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@RedsGT RedsGT changed the title Add Cosori Dual Blaze (CAF-P583S) air fryer support feat: add Cosori Dual Blaze (CAF-P583S) air fryer support May 10, 2026
RedsGT added 4 commits May 16, 2026 07:53
The bypassV2 payload_method was misspelled as 'getAirfyerStatus' (missing 'r' between 'f' and 'y'), causing VeSync's cloud to return 'Invalid bypass parameter' (code 11000000) on every status poll. The fixture in src/tests/api/vesynckitchen/CAF-DC601S.yaml mirrored the same typo, which is why tests passed despite the bug. This affects status polling for all TurboBlaze-class fryers (CAF-DC601S and CAF-P583S).
The TurboBlaze cook-request dataclass had three bugs that prevented
set_mode_from_recipe from ever succeeding — including for the
existing CAF-DC601S, not just CAF-P583S:

1. Missing 'mode' field. _build_cook_request() sets cook_req['mode']
   = recipe.cook_mode, but FryerTurboBlazeRequestData had no such
   field, so from_dict() rejected it with 'Serialized dict has keys
   that are not defined: mode'.

2. startAct typed as list[FryerTurboBlazeStartActItem] but
   _build_cook_request() always passes a single dict, not a list.
   Type mismatch on deserialization.

3. hasPreheat declared required but only set when recipe.preheat_time
   is truthy. Most presets (Air Fry, Broil, etc.) have no preheat
   time, so from_dict() would fail with 'missing required field'.

Also adds hasLinkage: bool = False to match the wire payload shape
that the VeSync mobile app (and the older WFON patch from
mikealanni's PR webdjoe#477 workaround) sends. The cloud may or may not
require it, but including it costs nothing and matches a known-good
known-working request.

This change is what makes 'remote start verified' actually
verifiable on the new code path. Without it, no TurboBlaze can
start a cook through set_mode_from_recipe.
On TurboBlaze fryers (CAF-DC601S, CAF-P583S Dual Blaze), the
'readyStart' field in the bypassV2 startCook payload has semantics
that are the inverse of what the name suggests:

  readyStart=true  -> device enters 'ready' state, beeps, and waits
                      for the user to physically press the Start
                      button before actually cooking.
  readyStart=false -> device begins cooking immediately on receipt.

Live debug from a Dual Blaze (CAF-P583S-KUS) firmware v1.0.15:
startCook with readyStart=true returns code=0/message=Success, but
the subsequent getAirfryerStatus shows cookStatus='ready' rather
than 'cooking', and the device never progresses past the beep until
a physical button press.

mikealanni's working WFON workaround (issue webdjoe#477) used
readyStart=false and that succeeded as true remote-start, which
this commit aligns the upstream code with.

If a downstream consumer eventually wants the 'stage and prompt'
behavior, the recipe dataclass can grow a 'ready_start: bool' field
with a default of False.
The bypassV2 getAirfryerStatus endpoint reports currentTemp (the
hardware sensor reading) in Celsius regardless of the response's
tempUnit field, which only governs the echoed cookTemp and
preheatTemp values. Consumers comparing cook_temp (the cook
setpoint, in tempUnit) against current_temp would see false
discrepancies — e.g., during a 360 F cook on a US (tempUnit=f)
fryer, currentTemp climbs from ~70 (= room temp in C) through
the cook range, hitting ~182 at the setpoint (= 360 F in C),
which downstream code interprets as 182 F and badly misreports.

Live evidence from a CAF-P583S-KUS firmware v1.0.15 cook at
360 F:
  cookTemp:   360
  tempUnit:   'f'
  currentTemp progression: 155, 160, 165, 170, 172, 177, 182, 184
  (clearly Celsius — plateaus at ~setpoint in C, not F)

In get_details, convert currentTemp from C to F when
resp_model.tempUnit == 'f'. For tempUnit == 'c', pass through
unchanged (already in the right unit). This affects the existing
CAF-DC601S TurboBlaze identically; that integration's
current_temp readings were almost certainly mislabeled too,
just less obvious without a setpoint to compare against.
return models.FryerTurboBlazeRequestData.from_dict(cook_req)

async def get_details(self) -> None:
resp = await self.call_bypassv2_api(payload_method='getAirfyerStatus')
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THANK YOU!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants