Skip to content

Commit 07bcdaa

Browse files
mwbourgeoisclaude
andcommitted
Expose air filter expiry date as HA timestamp sensor
Parses air_filter_end_date from fridge BLE payloads, promotes YYYY-MM-DD to ISO8601, and surfaces it as a text_sensor with device_class: timestamp. Gated by the existing hide_air_filter config flag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a84c7ae commit 07bcdaa

10 files changed

Lines changed: 34 additions & 0 deletions

File tree

components/subzero_appliance/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,16 @@
427427
},
428428
"hide_water_filter",
429429
),
430+
(
431+
"air_filter_end_date",
432+
"Air Filter Expires",
433+
"set_air_filter_end_date_sensor",
434+
{
435+
CONF_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
436+
CONF_ICON: "mdi:calendar-clock",
437+
},
438+
"hide_air_filter",
439+
),
430440
]
431441

432442
# Writable numbers — entry shape: (suffix, name_suffix, setter, property_key,

components/subzero_appliance/appliance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class FridgeAppliance : public ApplianceBase {
8787
void set_water_filter_end_date_sensor(esphome::text_sensor::TextSensor *s) {
8888
bus_.water_filter_end_date = s;
8989
}
90+
void set_air_filter_end_date_sensor(esphome::text_sensor::TextSensor *s) {
91+
bus_.air_filter_end_date = s;
92+
}
9093

9194
protected:
9295
SubzeroHub *hub() override { return &hub_; }

components/subzero_protocol/dispatch.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ inline void dispatch_fridge(const FridgeState &s, Bus &bus) {
108108
bus.publish_water_filter_gal(*s.water_filter_gal_remaining);
109109
if (s.water_filter_end_date)
110110
bus.publish_water_filter_end_date(*s.water_filter_end_date);
111+
if (s.air_filter_end_date)
112+
bus.publish_air_filter_end_date(*s.air_filter_end_date);
111113
}
112114

113115
template <typename Bus>

components/subzero_protocol/dispatch_esphome.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ struct FridgeBus : CommonBus {
142142
esphome::sensor::Sensor *water_filter_pct = nullptr;
143143
esphome::sensor::Sensor *water_filter_gal = nullptr;
144144
esphome::text_sensor::TextSensor *water_filter_end_date = nullptr;
145+
esphome::text_sensor::TextSensor *air_filter_end_date = nullptr;
145146

146147
void publish_door_ajar(bool v) { detail::publish_if(door_ajar, v); }
147148
void publish_frz_door_ajar(bool v) { detail::publish_if(frz_door_ajar, v); }
@@ -175,6 +176,9 @@ struct FridgeBus : CommonBus {
175176
void publish_water_filter_end_date(const std::string &v) {
176177
detail::publish_if(water_filter_end_date, v);
177178
}
179+
void publish_air_filter_end_date(const std::string &v) {
180+
detail::publish_if(air_filter_end_date, v);
181+
}
178182
};
179183

180184
struct DishwasherBus : CommonBus {

components/subzero_protocol/protocol.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ FridgeState parse_fridge(const std::string &json) {
278278
state.water_filter_end_date = v;
279279
}
280280
}
281+
if (auto raw = opt_str(data["air_filter_end_date"])) {
282+
const std::string &v = *raw;
283+
if (v.size() == 10 && v[4] == '-' && v[7] == '-') {
284+
state.air_filter_end_date = v + "T00:00:00+00:00";
285+
} else {
286+
state.air_filter_end_date = v;
287+
}
288+
}
281289
return state;
282290
}
283291

components/subzero_protocol/protocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ struct FridgeState {
6060
// to an ISO8601 timestamp ("YYYY-MM-DDT00:00:00+00:00") by the parser so
6161
// Home Assistant's timestamp device_class accepts it.
6262
std::optional<std::string> water_filter_end_date;
63+
std::optional<std::string> air_filter_end_date;
6364
};
6465

6566
struct DishwasherState {

tests/cpp/dispatch_test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ struct FridgeRecorder : CommonRecorder {
7575
void publish_water_filter_end_date(const std::string &v) {
7676
strings["water_filter_end_date"] = v;
7777
}
78+
void publish_air_filter_end_date(const std::string &v) {
79+
strings["air_filter_end_date"] = v;
80+
}
7881
};
7982

8083
struct DishwasherRecorder : CommonRecorder {

tests/cpp/protocol_test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ json fridge_to_json(const FridgeState &s) {
111111
OPT_PUT(o, s, water_filter_pct_remaining);
112112
OPT_PUT(o, s, water_filter_gal_remaining);
113113
OPT_PUT(o, s, water_filter_end_date);
114+
OPT_PUT(o, s, air_filter_end_date);
114115
return o;
115116
}
116117

tests/fixtures/fridge_back_2028_d5_full.expected.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"water_filter_pct_remaining": 0,
1111
"water_filter_gal_remaining": 107,
1212
"water_filter_end_date": "2023-10-04T00:00:00+00:00",
13+
"air_filter_end_date": "2024-05-11T00:00:00+00:00",
1314
"common": {
1415
"sabbath_on": false,
1516
"service_required": false,

tests/fixtures/fridge_pro3650g_d4_full.expected.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"water_filter_pct_remaining": 100,
1212
"water_filter_gal_remaining": 220,
1313
"water_filter_end_date": "2027-04-26T00:00:00+00:00",
14+
"air_filter_end_date": "2027-04-16T00:00:00+00:00",
1415
"common": {
1516
"sabbath_on": false,
1617
"service_required": false,

0 commit comments

Comments
 (0)