Skip to content

Commit 8ed0c38

Browse files
feat: add FireFeatures dataclass and parse feature flags from API
Add a FireFeatures frozen dataclass with 24 boolean feature flags matching the upstream FlameConnect API's FireFeature object. Parse these flags in both get_fires() and get_fire_overview() client methods, storing them on Fire.features. Update test fixtures with representative feature data and add tests for parsing and defaults. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8cffcf9 commit 8ed0c38

10 files changed

Lines changed: 439 additions & 1 deletion

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
id: 15
3+
summary: "Expose optional fire feature flags from the API and display them in CLI status output"
4+
created: 2026-02-27
5+
---
6+
7+
# Plan: Expose Fire Feature Flags in Library and CLI Status
8+
9+
## Original Work Order
10+
> Investigate how to best expose optional fire features in this library. This data may already be available, but check for each one. Then, make it so the CLI tool shows the status all optional features when showing fireplace status. Here is the list.
11+
>
12+
> Sound, SimpleHeat, AdvancedHeat, SevenDayTimer, CountDownTimer, Moods, FlameHeight, RgbFlameAccent, FlameDimming, RgbFuelBed, FuelBedDimming, FlameFanSpeed, RgbBackLight, FrontLightAmber, PirToggleSmartSense, Lgt1To5, RequiresWarmUp, ApplyFlameOnlyFirst, FlameAmber, CheckIfRemoteWasUsed, MediaAccent, PowerBoost, FanOnly, RgbLogEffect
13+
14+
## Executive Summary
15+
16+
The upstream FlameConnect API returns a `FireFeature` object as a nested property on each `Fire` object. This object contains 24 boolean flags that indicate which optional hardware features a specific fireplace model supports (e.g., heat, sound, log effect, media accent lighting). The current Python library **completely ignores** this data during deserialization of both the `GetFires` and `GetFireOverview` API responses.
17+
18+
This plan adds a `FireFeatures` dataclass to the models, parses the `FireFeature` JSON from both API endpoints, stores it on the `Fire` dataclass, and displays all 24 feature flags in the CLI `status` command output.
19+
20+
## Context
21+
22+
### Current State vs Target State
23+
24+
| Current State | Target State | Why? |
25+
|---|---|---|
26+
| `Fire` dataclass has no feature flag fields | `Fire` includes a `FireFeatures` dataclass | Feature availability data is needed to know what a model supports |
27+
| `get_fires()` ignores `FireFeature` in API JSON | `get_fires()` parses `FireFeature` into `FireFeatures` | Data is returned by the API but discarded |
28+
| `get_fire_overview()` ignores `FireFeature` in API JSON | `get_fire_overview()` parses `FireFeature` into `FireFeatures` | Same — data is available but unused |
29+
| CLI `status` shows only live parameters | CLI `status` also shows a "Supported Features" section | Users need to see what their fireplace model supports |
30+
| Test fixtures lack `FireFeature` data | Test fixtures include `FireFeature` JSON | Tests must cover the new parsing logic |
31+
32+
### Background
33+
34+
The decompiled Android app reveals the following about the API response structure:
35+
36+
1. **`FireFeature` is a property on the `Fire` class** (`Fire.cs:575-591`) — it is a nested JSON object, not a separate API call.
37+
38+
2. **`GetFires` returns `List<Fire>`** — each `Fire` object in the JSON array contains an inline `"FireFeature"` object. The current Python `get_fires()` reads from this array but ignores the `"FireFeature"` key.
39+
40+
3. **`GetFireOverview` returns `WiFiFireOverviewResult`** (`WiFiFireOverviewResult.cs`), which has three top-level keys:
41+
- `"ResultCode"` (short) — currently ignored by Python client
42+
- `"FireDetails"` (a `Fire` object) — **this is where `FireFeature` lives**
43+
- `"WiFiFireOverview"` (parameters + FireId only)
44+
45+
The C# `WiFiFireOverview` class (`WiFiFireOverview.cs`) only has `FireId` and `Parameters` — it does **not** have `FireFeature`. The decompiled app reads `Fire` from `result.FireDetails` (`BaseViewModelWiFiFire.cs:429`).
46+
47+
However, the current Python client reads fire identity fields (`FriendlyName`, `Brand`, etc.) from `data["WifiFireOverview"]`, suggesting the API may duplicate some fire fields into both locations. The existing test fixture only has a `"WifiFireOverview"` key with no `"FireDetails"`.
48+
49+
4. **The JSON property name is `"FireFeature"`** (PascalCase, matching C# conventions). All 24 boolean fields also use PascalCase names (e.g., `"SimpleHeat"`, `"RgbFlameAccent"`).
50+
51+
## Architectural Approach
52+
53+
```mermaid
54+
graph TD
55+
A[API JSON Response] -->|GetFires| B[client.get_fires]
56+
A -->|GetFireOverview| C[client.get_fire_overview]
57+
B --> D[Parse FireFeature from each Fire dict]
58+
C --> E[Parse FireFeature from response]
59+
D --> F[FireFeatures dataclass]
60+
E --> F
61+
F --> G[Stored on Fire.features]
62+
G --> H[CLI cmd_status displays features]
63+
```
64+
65+
### Data Model Addition
66+
67+
**Objective**: Add a `FireFeatures` frozen dataclass to `models.py` that holds all 24 boolean feature flags.
68+
69+
The dataclass will use Pythonic snake_case field names mapped from the PascalCase API keys. Each field defaults to `False` so that missing keys in the API response are handled gracefully. The `Fire` dataclass gains a new `features: FireFeatures` field with a default of `FireFeatures()` (all false) to maintain backward compatibility — since `FireFeatures` is frozen/immutable, a plain default value works without `field(default_factory=...)`.
70+
71+
The 24 boolean fields:
72+
`sound`, `simple_heat`, `advanced_heat`, `seven_day_timer`, `count_down_timer`, `moods`, `flame_height`, `rgb_flame_accent`, `flame_dimming`, `rgb_fuel_bed`, `fuel_bed_dimming`, `flame_fan_speed`, `rgb_back_light`, `front_light_amber`, `pir_toggle_smart_sense`, `lgt1_to_5`, `requires_warm_up`, `apply_flame_only_first`, `flame_amber`, `check_if_remote_was_used`, `media_accent`, `power_boost`, `fan_only`, `rgb_log_effect`
73+
74+
### Client Parsing
75+
76+
**Objective**: Extract `FireFeature` from API responses and construct `FireFeatures` instances.
77+
78+
A private helper function `_parse_fire_features(data: dict[str, Any]) -> FireFeatures` in `client.py` will map PascalCase JSON keys to the dataclass fields. It will use `.get(key, False)` for each field so missing keys default to `False`. If `data` is empty or `None`, return `FireFeatures()` (all false).
79+
80+
**`get_fires()`**: Each fire dict in the response array contains a `"FireFeature"` key. Pass `entry.get("FireFeature", {})` to the helper.
81+
82+
**`get_fire_overview()`**: Based on the C# `WiFiFireOverviewResult` structure, `FireFeature` is on the `FireDetails` object, not on `WifiFireOverview`. The parsing strategy should check multiple locations in priority order:
83+
1. `data.get("FireDetails", {}).get("FireFeature", {})` — the canonical location per the C# model
84+
2. `wifi.get("FireFeature", {})` — fallback in case the API duplicates it here
85+
86+
Use the first non-empty dict found. This handles both the expected structure and any API quirks.
87+
88+
### CLI Status Display
89+
90+
**Objective**: Show a "Supported Features" section in `cmd_status` output listing all 24 feature flags.
91+
92+
Add a `_display_features(features: FireFeatures)` function in `cli.py` that prints a section after the fire identity info (Fireplace name + Connection state) and before the parameter list. The display will show all 24 features with Yes/No values, matching the user's request to "show the status of ALL optional features." Use a human-readable label for each field (e.g., `simple_heat``Simple Heat`). Format as a compact list consistent with the existing `_display_*` style.
93+
94+
### Test Fixture and Test Updates
95+
96+
**Objective**: Ensure the new parsing and display logic is tested.
97+
98+
- Update `tests/fixtures/get_fires.json` to include a `"FireFeature"` object on each fire entry with a representative mix of `true` and `false` values.
99+
- Update `tests/fixtures/get_fire_overview.json` to include `"FireFeature"` in a location the client will find (within the existing `"WifiFireOverview"` object is simplest, since that's where the Python client reads fire identity from; optionally also add a top-level `"FireDetails"` key).
100+
- Add tests in `test_client.py` to verify `Fire.features` is correctly populated from both `get_fires()` and `get_fire_overview()`.
101+
- Add tests in `test_cli_commands.py` to verify the status output includes the features section.
102+
- Add a test for the `FireFeatures` dataclass defaults in `test_models.py` (all fields `False` by default).
103+
104+
## Risk Considerations and Mitigation Strategies
105+
106+
<details>
107+
<summary>Technical Risks</summary>
108+
109+
- **Exact JSON location of FireFeature in GetFireOverview is uncertain**: The C# model places it on `FireDetails`, but the Python client reads fire identity from `WifiFireOverview` (suggesting the API may flatten or duplicate fields). The existing test fixture has no `FireDetails` key.
110+
- **Mitigation**: Check both `data["FireDetails"]["FireFeature"]` and `data["WifiFireOverview"]["FireFeature"]` in priority order. Default to all-false if absent in both. A live API call during development can confirm the exact structure (per project philosophy).
111+
112+
- **API may not return FireFeature for all accounts/models**: Older firmware or API versions might omit the field entirely.
113+
- **Mitigation**: All fields default to `False`, so a missing `FireFeature` key means "no features reported" rather than an error.
114+
</details>
115+
116+
<details>
117+
<summary>Implementation Risks</summary>
118+
119+
- **Breaking existing tests**: Adding a `features` field to `Fire` changes its constructor signature.
120+
- **Mitigation**: Use a default value (`features: FireFeatures = FireFeatures()`) so existing `Fire(...)` calls without the new field continue to work unchanged.
121+
</details>
122+
123+
## Success Criteria
124+
125+
### Primary Success Criteria
126+
1. `FireFeatures` dataclass exists in `models.py` with all 24 boolean fields
127+
2. `client.get_fires()` and `client.get_fire_overview()` populate `Fire.features` from API JSON when present
128+
3. `flameconnect status` CLI command displays a "Supported Features" section listing all 24 feature flags with Yes/No values
129+
4. All existing tests continue to pass
130+
5. New tests cover feature parsing and display
131+
6. `uv run ruff check .` and `uv run mypy src/` pass cleanly
132+
133+
## Resource Requirements
134+
135+
### Development Skills
136+
- Python dataclasses and type annotations
137+
- Async API client patterns (existing codebase conventions)
138+
- CLI output formatting (matching existing `_display_*` style)
139+
140+
### Technical Infrastructure
141+
- Python 3.13+, uv, ruff, mypy, pytest (all already in project)
142+
143+
## Notes
144+
145+
- The `PresetOptions` and `FlamePresetOptions` short fields from the decompiled app are excluded since the user's request only covers the 24 boolean flags.
146+
- The TUI is out of scope for this plan — the user only asked about CLI status. TUI integration can be a follow-up if desired.
147+
- This plan does **not** add feature-gating logic (hiding unsupported controls). That is a separate concern for future work.
148+
149+
### Change Log
150+
- 2026-02-27: Initial plan creation.
151+
- 2026-02-27: Refined client parsing strategy — corrected `GetFireOverview` search order to check `FireDetails` first (matching C# `WiFiFireOverviewResult` structure confirmed via `BaseViewModelWiFiFire.cs:429`). Specified CLI display format as all 24 flags with Yes/No. Removed out-of-scope TUI/HA node from architecture diagram. Clarified dataclass default value approach. Added detail about test fixture `FireDetails` key.
152+
153+
## Task Dependencies
154+
155+
```mermaid
156+
graph TD
157+
01[Task 01: Add FireFeatures Model + Client Parsing] --> 02[Task 02: CLI Status Display]
158+
```
159+
160+
## Execution Blueprint
161+
162+
**Validation Gates:**
163+
- Reference: `/config/hooks/POST_PHASE.md`
164+
165+
### Phase 1: Data Model and Client Parsing
166+
**Parallel Tasks:**
167+
- Task 01: Add FireFeatures dataclass, update Fire model, add client parsing, update fixtures and tests
168+
169+
### Phase 2: CLI Integration
170+
**Parallel Tasks:**
171+
- Task 02: Display fire feature flags in CLI status command (depends on: 01)
172+
173+
### Execution Summary
174+
- Total Phases: 2
175+
- Total Tasks: 2
176+
- Maximum Parallelism: 1 task
177+
- Critical Path Length: 2 phases
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
id: 1
3+
group: "fire-feature-flags"
4+
dependencies: []
5+
status: "pending"
6+
created: 2026-02-27
7+
skills:
8+
- python-dataclasses
9+
---
10+
# Add FireFeatures Dataclass and Update Fire Model
11+
12+
## Objective
13+
Add a `FireFeatures` frozen dataclass to `models.py` with all 24 boolean feature flags, and add a `features` field to the `Fire` dataclass. Also add a `_parse_fire_features()` helper to `client.py` and wire it into both `get_fires()` and `get_fire_overview()`.
14+
15+
## Skills Required
16+
- Python dataclasses and type annotations
17+
18+
## Acceptance Criteria
19+
- [ ] `FireFeatures` dataclass exists in `models.py` with all 24 boolean fields defaulting to `False`
20+
- [ ] `Fire` dataclass has a `features: FireFeatures` field with default `FireFeatures()`
21+
- [ ] `FireFeatures` is exported from `__init__.py`
22+
- [ ] `_parse_fire_features()` helper in `client.py` maps PascalCase JSON keys to snake_case fields
23+
- [ ] `get_fires()` populates `Fire.features` from `entry.get("FireFeature", {})`
24+
- [ ] `get_fire_overview()` populates `Fire.features` checking `data.get("FireDetails", {}).get("FireFeature", {})` first, then `wifi.get("FireFeature", {})` as fallback
25+
- [ ] Test fixtures updated with `"FireFeature"` data
26+
- [ ] New tests for `FireFeatures` defaults, `_parse_fire_features`, and both client methods
27+
- [ ] `uv run ruff check .` passes
28+
- [ ] `uv run mypy src/` passes
29+
- [ ] `uv run pytest` passes (all existing + new tests)
30+
31+
## Technical Requirements
32+
- `FireFeatures` must be `@dataclass(frozen=True, slots=True)` matching existing conventions
33+
- The 24 boolean fields (in snake_case): `sound`, `simple_heat`, `advanced_heat`, `seven_day_timer`, `count_down_timer`, `moods`, `flame_height`, `rgb_flame_accent`, `flame_dimming`, `rgb_fuel_bed`, `fuel_bed_dimming`, `flame_fan_speed`, `rgb_back_light`, `front_light_amber`, `pir_toggle_smart_sense`, `lgt1_to_5`, `requires_warm_up`, `apply_flame_only_first`, `flame_amber`, `check_if_remote_was_used`, `media_accent`, `power_boost`, `fan_only`, `rgb_log_effect`
34+
- PascalCase API key mapping: `Sound`, `SimpleHeat`, `AdvancedHeat`, `SevenDayTimer`, `CountDownTimer`, `Moods`, `FlameHeight`, `RgbFlameAccent`, `FlameDimming`, `RgbFuelBed`, `FuelBedDimming`, `FlameFanSpeed`, `RgbBackLight`, `FrontLightAmber`, `PirToggleSmartSense`, `Lgt1To5`, `RequiresWarmUp`, `ApplyFlameOnlyFirst`, `FlameAmber`, `CheckIfRemoteWasUsed`, `MediaAccent`, `PowerBoost`, `FanOnly`, `RgbLogEffect`
35+
- `features` field must come AFTER existing `Fire` fields (with a default) to avoid breaking existing constructor calls
36+
37+
## Input Dependencies
38+
None — this is the foundation task.
39+
40+
## Output Artifacts
41+
- Updated `src/flameconnect/models.py` with `FireFeatures` dataclass and updated `Fire`
42+
- Updated `src/flameconnect/__init__.py` with `FireFeatures` export
43+
- Updated `src/flameconnect/client.py` with `_parse_fire_features()` and updated `get_fires()`/`get_fire_overview()`
44+
- Updated test fixtures and new tests
45+
46+
## Implementation Notes
47+
- Follow the existing code conventions (frozen dataclasses, slots=True, type annotations)
48+
- The `_parse_fire_features` helper should accept `dict[str, Any]` and use `.get(key, False)` for each field
49+
- For `get_fire_overview()`, check `data.get("FireDetails", {}).get("FireFeature", {})` first (canonical location per C# model), then `wifi.get("FireFeature", {})` as fallback
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
id: 2
3+
group: "fire-feature-flags"
4+
dependencies: [1]
5+
status: "pending"
6+
created: 2026-02-27
7+
skills:
8+
- python-cli
9+
---
10+
# Display Fire Feature Flags in CLI Status Command
11+
12+
## Objective
13+
Add a `_display_features()` function to `cli.py` that shows all 24 fire feature flags with Yes/No values in the `cmd_status` output, and add tests for it.
14+
15+
## Skills Required
16+
- Python CLI output formatting
17+
18+
## Acceptance Criteria
19+
- [ ] `_display_features(features: FireFeatures)` function exists in `cli.py`
20+
- [ ] `cmd_status` calls `_display_features` after fire identity info and before parameters
21+
- [ ] All 24 feature flags are displayed with human-readable labels and Yes/No values
22+
- [ ] Tests in `test_cli_commands.py` verify the features section appears in status output
23+
- [ ] `uv run ruff check .` passes
24+
- [ ] `uv run mypy src/` passes
25+
- [ ] `uv run pytest` passes
26+
27+
## Technical Requirements
28+
- Display format should match existing `_display_*` style in `cli.py` (indented, with section header)
29+
- Human-readable labels derived from field names (e.g., `simple_heat``Simple Heat`)
30+
- Section header: "Supported Features"
31+
- Show all 24 features with Yes/No values
32+
33+
## Input Dependencies
34+
- Task 01: `FireFeatures` dataclass in `models.py`, `Fire.features` field populated by client
35+
36+
## Output Artifacts
37+
- Updated `src/flameconnect/cli.py` with `_display_features()` and updated `cmd_status`
38+
- New tests in `tests/test_cli_commands.py`
39+
40+
## Implementation Notes
41+
- Place the features section between the Connection line and the parameter count line
42+
- Use the same indentation style as other display functions (2-space indent for header, 4-space for values)
43+
- Match the `_display_*` naming convention

src/flameconnect/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ConnectionState,
1919
ErrorParam,
2020
Fire,
21+
FireFeatures,
2122
FireMode,
2223
FireOverview,
2324
FlameColor,
@@ -75,6 +76,7 @@
7576
# Dataclasses
7677
"ErrorParam",
7778
"Fire",
79+
"FireFeatures",
7880
"FireOverview",
7981
"FlameEffectParam",
8082
"HeatModeParam",

0 commit comments

Comments
 (0)