Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
proto_device_types.cpp
Go to the documentation of this file.
1/// @file proto_device_types.cpp
2/// @brief The IO node-type / subtype table and its lookups.
3/// @ingroup hioc_protocol
4
6
7#include <algorithm>
8#include <cstdio>
9#include <iterator>
10
11namespace esphome {
12namespace home_io_control {
13
14namespace {
15
16using device_feature::EXTENDED_POSITION_FRAME;
17using device_feature::FAVORITE;
18using device_feature::INVERTED;
19using device_feature::LEVELS;
20using device_feature::PARTIAL;
21using device_feature::PEDESTRIAN;
22using device_feature::TILT;
23using device_feature::VENT;
24using device_feature::WINDOW_COMPANIONS;
25using device_type_flags::EXPERIMENTAL;
26using device_type_flags::HAS_PLATFORM;
27using device_type_flags::STANDALONE_NAME;
28using device_type_flags::YAML_SELECTABLE;
29
38
39constexpr uint8_t SELECTABLE_PLATFORM =
40 YAML_SELECTABLE | HAS_PLATFORM; ///< Type row that YAML can name and a platform drives.
41constexpr uint8_t SUBTYPE_PLATFORM = HAS_PLATFORM; ///< Subtype row a platform drives.
42constexpr uint8_t EXPERIMENTAL_SUBTYPE_PLATFORM =
43 HAS_PLATFORM | EXPERIMENTAL; ///< Subtype row a platform drives that no capture or report confirms.
44
45// === Feature words ===
46//
47// A row states its whole word. Families share a constant, and a subtype-58 row is
48// main_parameter_only_variant() of its family where it has no reason to differ.
49
50/// Positionable cover: a slider, the favourite position and the 8-byte position frame.
51constexpr DeviceFeatures COVER = LEVELS | FAVORITE | EXTENDED_POSITION_FRAME;
52/// Cover with an adjustable slat angle.
53constexpr DeviceFeatures COVER_TILTING = COVER | TILT;
54/// Interior venetian blind (type 1): functional parameter 1 is the slat orientation, not a travel
55/// speed (VELUX KLF 200 API specification, Appendix 2), so positions go out in the 6-byte form
56/// without EXTENDED_POSITION_FRAME; tilt stays on functional parameter 3.
57/// \todo Confirm on a real two-way interior venetian blind that the 6-byte position frame moves it and
58/// that the tilt frame on functional parameter 3 still orients the slats (experimental).
59constexpr DeviceFeatures INTERIOR_VENETIAN = LEVELS | FAVORITE | TILT;
60/// Dual roller shutter (type 13): functional parameter 1 is the upper curtain and the speed is
61/// parameter 4 (VELUX KLF 200 API specification, Appendix 2), so positions go out in the 6-byte form.
62/// \todo Confirm on a real two-way dual roller shutter that the 6-byte position frame moves both
63/// curtains and that a position needs no functional parameter (experimental).
64constexpr DeviceFeatures DUAL_SHUTTER = LEVELS | FAVORITE;
65/// Vertical interior blind (type 10): the KLF 200 parameter table lists no slat parameter for it
66/// (VELUX KLF 200 API specification, Appendix 2), so it is a plain cover.
67/// \todo Confirm on a real two-way vertical interior blind that it has no slat angle (experimental).
68constexpr DeviceFeatures VERTICAL_INTERIOR = COVER;
69/// Window opener: a cover with the secured ventilation position.
70constexpr DeviceFeatures WINDOW = COVER | VENT;
71/// Garage opener: percent positions in the main parameter only, and no favourite position. The
72/// garage opener of issue #163 refuses the favourite alias with PARAMETER_UNKNOWN (corpus capture
73/// wisniowski_moto1000_exchange_execute_parameter_unknown); that rests on one device of this type,
74/// so it is applied to the whole family.
75/// It also carries PARTIAL, the Partial Position button (0xD809), which runs the opener's stored
76/// partial position (confirmed on the issue #163 opener).
77constexpr DeviceFeatures GARAGE = device_feature::MAIN_PARAMETER_ONLY | LEVELS | PARTIAL;
78/// Gate opener: a positionable cover that also carries PEDESTRIAN, the Pedestrian Position button
79/// (0xD807).
80/// \todo Confirm on a real gate opener that 0xD807 opens it to a pedestrian position (experimental).
81constexpr DeviceFeatures GATE = COVER | PEDESTRIAN;
82/// On/off command in the 8-byte frame: a plain light, a switch, a lock.
83constexpr DeviceFeatures BINARY_COMMAND = EXTENDED_POSITION_FRAME;
84/// Light whose brightness is controllable (the 6/1 and 6/2 rows).
85constexpr DeviceFeatures DIMMABLE_LIGHT = LEVELS | EXTENDED_POSITION_FRAME;
86/// Exterior heating: a heat level in the main parameter, main parameter only.
87constexpr DeviceFeatures HEAT_LEVEL_DEVICE = device_feature::MAIN_PARAMETER_ONLY | LEVELS;
88
89/// Model of every type and subtype row: the row is the (type, subtype) itself.
90constexpr DeviceModel NO_MODEL = DeviceModel::NONE;
91
92constexpr size_t TYPE_HEX_BUFFER_SIZE = 24; ///< "unknown type 0x3FF" plus margin.
93
94/// @brief The table, sorted by (type, subtype); a model row follows the (type, subtype) row it belongs to.
95///
96/// A row without HAS_PLATFORM is a type the project recognises and names but cannot drive yet:
97/// discovery and the roll-call scan report it as "not supported yet" and print no YAML.
98/// The capability class is the entity family the device would map to, so a type with a class but no
99/// platform (ventilation point, heating interface) is a statement about the device, not a promise.
100///
101/// Rows that no capture or report has confirmed yet carry device_type_flags::EXPERIMENTAL: the 6/1,
102/// 6/2, 7/58, 9/1, 9/2, 15/2, 22/1, 22/2, 24/58, 27/1 and 1023/2..5 subtype rows. Discovery and pairing
103/// log them as experimental and the codegen warns when a config declares one. The flag leaves with the
104/// \todo that names a row once a capture or a reporter confirms it. Rows 1, 10 and 13 are not flagged:
105/// their changed behaviour is experimental, their identity is not. The 51/0 type row is the one type row
106/// that carries the flag: one heater of one brand has been read, its writes are unconfirmed, and the flag
107/// is what makes the codegen warn a user of any other water heater. The flag names its row only: a
108/// subtype without a row of its own does not inherit it (device_is_experimental(), and the Python mirror).
109/// \todo Confirm 6/1 and 6/2 accept the brightness EXECUTE on a real device.
110/// \todo Confirm the 15/2, 9/1, 9/2, 22/x, 7/58, 24/58, 27/1 and 1023/2..5 subtype rows against a capture.
111constexpr DeviceTypeInfo DEVICE_TYPE_TABLE[] = {
112 {DeviceType::UNKNOWN, 0, NO_MODEL, "unknown", CAP_NONE, YAML_SELECTABLE, 0},
113
114 {DeviceType::INTERIOR_VENETIAN_BLIND, 0, NO_MODEL, "interior_venetian_blind", CAP_COVER, SELECTABLE_PLATFORM,
115 INTERIOR_VENETIAN},
116
117 {DeviceType::ROLLER_SHUTTER, 0, NO_MODEL, "roller_shutter", CAP_COVER, SELECTABLE_PLATFORM, COVER},
118 {DeviceType::ROLLER_SHUTTER, 1, NO_MODEL, "with adjustable slats", CAP_COVER, SUBTYPE_PLATFORM, COVER},
119 {DeviceType::ROLLER_SHUTTER, 2, NO_MODEL, "with projection", CAP_COVER, SUBTYPE_PLATFORM, COVER},
120
121 {DeviceType::VERTICAL_EXTERIOR_AWNING, 0, NO_MODEL, "vertical_exterior_awning", CAP_COVER, SELECTABLE_PLATFORM,
122 COVER},
123
124 {DeviceType::WINDOW_OPENER, 0, NO_MODEL, "window_opener", CAP_COVER, SELECTABLE_PLATFORM, WINDOW},
125 {DeviceType::WINDOW_OPENER, 1, NO_MODEL, "with integrated rain sensor", CAP_COVER, SUBTYPE_PLATFORM, WINDOW},
126
127 // Every garage opener takes the main parameter only (issue #163); subtype 58 does so on any type.
128 {DeviceType::GARAGE_OPENER, 0, NO_MODEL, "garage_opener", CAP_COVER, SELECTABLE_PLATFORM, GARAGE},
129 // Subtype 58 offers open, close and stop only: the 5/58 garage opener of issue #163 refuses a 50 %
130 // position with PARAMETER_UNKNOWN (corpus capture wisniowski_moto1000_exchange_execute_partial_position_refused).
131 {DeviceType::GARAGE_OPENER, SUBTYPE_MAIN_PARAMETER_ONLY, NO_MODEL, "open/close only", CAP_COVER, SUBTYPE_PLATFORM,
133
134 {DeviceType::LIGHT, 0, NO_MODEL, "light", CAP_LIGHT, SELECTABLE_PLATFORM, BINARY_COMMAND},
135 {DeviceType::LIGHT, 1, NO_MODEL, "dimmable RGB", CAP_LIGHT, EXPERIMENTAL_SUBTYPE_PLATFORM, DIMMABLE_LIGHT},
136 {DeviceType::LIGHT, 2, NO_MODEL, "tunable white", CAP_LIGHT, EXPERIMENTAL_SUBTYPE_PLATFORM, DIMMABLE_LIGHT},
137 {DeviceType::LIGHT, SUBTYPE_MAIN_PARAMETER_ONLY, NO_MODEL, "on/off only", CAP_LIGHT, SUBTYPE_PLATFORM,
138 main_parameter_only_variant(BINARY_COMMAND)},
139
140 {DeviceType::GATE_OPENER, 0, NO_MODEL, "gate_opener", CAP_COVER, SELECTABLE_PLATFORM, GATE},
141 // \todo Confirm on a real 7/58 gate opener that intermediate positions are refused and whether the
142 // favourite is accepted; inferred from the shared subtype-58 definition and the 5/58 opener of
143 // issue #163 (experimental).
144 {DeviceType::GATE_OPENER, SUBTYPE_MAIN_PARAMETER_ONLY, NO_MODEL, "open/close only", CAP_COVER,
145 EXPERIMENTAL_SUBTYPE_PLATFORM, main_parameter_only_variant(GATE)},
146
147 {DeviceType::ROLLING_DOOR_OPENER, 0, NO_MODEL, "rolling_door_opener", CAP_COVER, SELECTABLE_PLATFORM, COVER},
148
149 {DeviceType::LOCK, 0, NO_MODEL, "lock", CAP_LOCK, SELECTABLE_PLATFORM, BINARY_COMMAND},
150 {DeviceType::LOCK, 1, NO_MODEL, "window lock", CAP_LOCK, EXPERIMENTAL_SUBTYPE_PLATFORM | STANDALONE_NAME,
151 BINARY_COMMAND},
152 {DeviceType::LOCK, 2, NO_MODEL, "car controller", CAP_NONE, EXPERIMENTAL | STANDALONE_NAME, 0},
153
154 {DeviceType::VERTICAL_INTERIOR_BLIND, 0, NO_MODEL, "vertical_interior_blind", CAP_COVER, SELECTABLE_PLATFORM,
155 VERTICAL_INTERIOR},
156
157 {DeviceType::SECURE_CONFIGURATION_DEVICE, 0, NO_MODEL, "secure_configuration_device", CAP_NONE, 0, 0},
158
159 {DeviceType::BEACON, 0, NO_MODEL, "beacon", DeviceCapabilityClass::BEACON, 0, 0},
160
161 {DeviceType::DUAL_ROLLER_SHUTTER, 0, NO_MODEL, "dual_roller_shutter", CAP_COVER, SELECTABLE_PLATFORM, DUAL_SHUTTER},
162
163 {DeviceType::HEATING_TEMPERATURE_INTERFACE, 0, NO_MODEL, "heating_temperature_interface", CAP_CLIMATE,
164 SELECTABLE_PLATFORM, 0},
165
166 {DeviceType::ON_OFF_SWITCH, 0, NO_MODEL, "on_off_switch", CAP_SWITCH, SELECTABLE_PLATFORM, BINARY_COMMAND},
167 {DeviceType::ON_OFF_SWITCH, 2, NO_MODEL, "micromodule", CAP_SWITCH, EXPERIMENTAL_SUBTYPE_PLATFORM, BINARY_COMMAND},
168
169 {DeviceType::HORIZONTAL_AWNING, 0, NO_MODEL, "horizontal_awning", CAP_COVER, SELECTABLE_PLATFORM, COVER | INVERTED},
170 {DeviceType::HORIZONTAL_AWNING, 1, NO_MODEL, "rail-guided pergola", CAP_COVER, SUBTYPE_PLATFORM, COVER | INVERTED},
171
172 {DeviceType::EXTERIOR_VENETIAN_BLIND, 0, NO_MODEL, "exterior_venetian_blind", CAP_COVER, SELECTABLE_PLATFORM,
173 COVER_TILTING},
174
175 {DeviceType::LOUVRE_BLIND, 0, NO_MODEL, "louvre_blind", CAP_COVER, SELECTABLE_PLATFORM, COVER_TILTING},
176
177 {DeviceType::CURTAIN_TRACK, 0, NO_MODEL, "curtain_track", CAP_COVER, SELECTABLE_PLATFORM, COVER},
178
179 {DeviceType::VENTILATION_POINT, 0, NO_MODEL, "ventilation_point", CAP_SWITCH, 0, 0},
180 {DeviceType::VENTILATION_POINT, 1, NO_MODEL, "air inlet", CAP_SWITCH, 0, 0},
181 {DeviceType::VENTILATION_POINT, 2, NO_MODEL, "air transfer", CAP_SWITCH, 0, 0},
182 {DeviceType::VENTILATION_POINT, 3, NO_MODEL, "air outlet", CAP_SWITCH, 0, 0},
183
184 // Exterior heating takes a heat level in the position field (a fan entity), not a setpoint. Every
185 // exterior heater gets the main parameter only: its first functional parameter is not a cover's, and
186 // a guessed block is what issue #163 showed to cost a refusal.
187 {DeviceType::EXTERIOR_HEATING, 0, NO_MODEL, "exterior_heating", CAP_HEATER_LEVEL, SELECTABLE_PLATFORM,
188 HEAT_LEVEL_DEVICE},
189 {DeviceType::EXTERIOR_HEATING, SUBTYPE_MAIN_PARAMETER_ONLY, NO_MODEL, "on/off only", CAP_HEATER_LEVEL,
190 SUBTYPE_PLATFORM, main_parameter_only_variant(HEAT_LEVEL_DEVICE)},
191
192 {DeviceType::HEAT_PUMP, 0, NO_MODEL, "heat_pump", CAP_CLIMATE, SELECTABLE_PLATFORM, 0},
193 // The two Atlantic profiles use a different private register family from the electrical
194 // heater's, so the heating payloads must not be sent to them: no climate class.
195 {DeviceType::HEAT_PUMP, 1, NO_MODEL, "heat pump (Atlantic)", CAP_NONE, EXPERIMENTAL | STANDALONE_NAME, 0},
196 {DeviceType::HEAT_PUMP, 2, NO_MODEL, "boiler (Atlantic)", CAP_NONE, EXPERIMENTAL | STANDALONE_NAME, 0},
197
198 {DeviceType::INTRUSION_ALARM, 0, NO_MODEL, "intrusion_alarm", DeviceCapabilityClass::SENSOR, YAML_SELECTABLE, 0},
199
200 {DeviceType::SWINGING_SHUTTER, 0, NO_MODEL, "swinging_shutter", CAP_COVER, SELECTABLE_PLATFORM, COVER},
201 {DeviceType::SWINGING_SHUTTER, 1, NO_MODEL, "with independent leaves", CAP_COVER, SUBTYPE_PLATFORM, COVER},
202 // \todo Confirm on a real 24/58 swinging shutter that the favourite is accepted and percent positions
203 // are refused; inferred from the shared subtype-58 definition and the 5/58 opener of issue #163
204 // (experimental).
205 {DeviceType::SWINGING_SHUTTER, SUBTYPE_MAIN_PARAMETER_ONLY, NO_MODEL, "open/close only", CAP_COVER,
206 EXPERIMENTAL_SUBTYPE_PLATFORM, main_parameter_only_variant(COVER)},
207
208 {DeviceType::SLIDING_WINDOW, 0, NO_MODEL, "sliding_window", CAP_COVER, SELECTABLE_PLATFORM, COVER},
209 {DeviceType::SLIDING_WINDOW, 1, NO_MODEL, "with lock", CAP_COVER, EXPERIMENTAL_SUBTYPE_PLATFORM, COVER},
210
211 {DeviceType::ZONE_CONTROL, 0, NO_MODEL, "zone_control", CAP_NONE, 0, 0},
212
213 {DeviceType::BIOCLIMATIC_PERGOLA, 0, NO_MODEL, "bioclimatic_pergola", CAP_COVER, SELECTABLE_PLATFORM, COVER},
214
215 {DeviceType::SIREN, 0, NO_MODEL, "siren", CAP_NONE, 0, 0},
216
217 // A domestic hot water heater: readings and mode in family-0x00 registers, the target in the status
218 // main parameter (proto_dhw.h). Read on one Atlantic Calypso Connecte (issue #198); its writes are
219 // unconfirmed.
220 // \todo Confirm on a real heater that the target EXECUTE and the mode write take effect, then drop
221 // EXPERIMENTAL.
222 {DeviceType::DOMESTIC_HOT_WATER, 0, NO_MODEL, "domestic_hot_water", CAP_WATER_HEATER,
223 SELECTABLE_PLATFORM | EXPERIMENTAL, device_feature::WATER_HEATER},
224
225 // Both rows have a heating profile (proto_heating_profiles.cpp): the plain heater is commanded with
226 // heating levels in the standard EXECUTE, the one with an adjustable setpoint over the private
227 // registers (proto_heating.h).
228 {DeviceType::ELECTRICAL_HEATER, 0, NO_MODEL, "electrical_heater", CAP_CLIMATE, SELECTABLE_PLATFORM, 0},
229 {DeviceType::ELECTRICAL_HEATER, 1, NO_MODEL, "with adjustable setpoint", CAP_CLIMATE, SUBTYPE_PLATFORM,
230 WINDOW_COMPANIONS},
231 // A towel dryer reports the same 52/1 as the AGILIA radiator (two Atlantic Adelis units in issue
232 // #202), so it is a model row: the YAML declares it (`io_model: towel_dryer`). It has no window
233 // sensor and no open-window detection, and its own heating profile (proto_heating_profiles.cpp).
234 {DeviceType::ELECTRICAL_HEATER, 1, DeviceModel::TOWEL_DRYER, "towel_dryer", CAP_CLIMATE, SUBTYPE_PLATFORM, 0},
235 // A heater with a night light (a Sauter Hekla, issue #222) reports the same 52/1 as well: everything the
236 // radiator has, plus the light's registers in its own heating profile.
237 {DeviceType::ELECTRICAL_HEATER, 1, DeviceModel::HEATER_WITH_LIGHT, "heater_with_light", CAP_CLIMATE,
238 SUBTYPE_PLATFORM, WINDOW_COMPANIONS | device_feature::NIGHT_LIGHT},
239
240 {DeviceType::HEAT_RECOVERY_VENTILATION, 0, NO_MODEL, "heat_recovery_ventilation", CAP_NONE, 0, 0},
241
242 {DeviceType::CENTRAL_HOUSE_CONTROL, 0, NO_MODEL, "central_house_control", CAP_NONE, 0, 0},
243
244 {DeviceType::TEST_AND_EVALUATION, 0, NO_MODEL, "test_and_evaluation", CAP_NONE, 0, 0},
245
246 {DeviceType::REMOTE_CONTROLLER, 0, NO_MODEL, "remote_controller", CAP_NONE, 0, 0},
247 {DeviceType::REMOTE_CONTROLLER, 2, NO_MODEL, "Keygo", CAP_NONE, EXPERIMENTAL, 0},
248 {DeviceType::REMOTE_CONTROLLER, 3, NO_MODEL, "Izymo", CAP_NONE, EXPERIMENTAL, 0},
249 {DeviceType::REMOTE_CONTROLLER, 4, NO_MODEL, "public frame", CAP_NONE, EXPERIMENTAL, 0},
250 {DeviceType::REMOTE_CONTROLLER, 5, NO_MODEL, "V500", CAP_NONE, EXPERIMENTAL, 0},
251};
252
253} // namespace
254
256 for (const DeviceTypeInfo &row : DEVICE_TYPE_TABLE) {
257 if (row.type == type && row.subtype == subtype && row.model == DeviceModel::NONE)
258 return &row;
259 }
260 return nullptr;
261}
262
263const DeviceTypeInfo *find_device_model_info(DeviceType type, uint8_t subtype, DeviceModel model) {
264 if (model == DeviceModel::NONE)
265 return nullptr;
266 for (const DeviceTypeInfo &row : DEVICE_TYPE_TABLE) {
267 if (row.type == type && row.subtype == subtype && row.model == model)
268 return &row;
269 }
270 return nullptr;
271}
272
273bool device_offers_models(DeviceType type, uint8_t subtype) {
274 return std::any_of(std::begin(DEVICE_TYPE_TABLE), std::end(DEVICE_TYPE_TABLE),
275 [type, subtype](const DeviceTypeInfo &row) {
276 return row.type == type && row.subtype == subtype && row.model != DeviceModel::NONE;
277 });
278}
279
280const DeviceTypeInfo *find_device_type_info(DeviceType type, uint8_t subtype, DeviceModel model) {
281 if (const DeviceTypeInfo *model_row = find_device_model_info(type, subtype, model))
282 return model_row;
283 const DeviceTypeInfo *row = find_exact_device_type_info(type, subtype);
284 return row != nullptr || subtype == 0 ? row : find_exact_device_type_info(type, 0);
285}
286
287// The platform, the class and the experimental flag are facts of the (type, subtype): a model row keeps
288// those of the row above it (device_types_table_test.cpp checks that), so these lookups take no model.
289
290bool device_has_platform(DeviceType type, uint8_t subtype) {
291 const DeviceTypeInfo *row = find_device_type_info(type, subtype, DeviceModel::NONE);
292 return row != nullptr && (row->flags & HAS_PLATFORM) != 0;
293}
294
295const char *device_platform_name(DeviceType type, uint8_t subtype) {
296 if (!device_has_platform(type, subtype))
297 return nullptr;
298 switch (find_device_type_info(type, subtype, DeviceModel::NONE)->capability) {
300 return "cover";
302 return "light";
304 return "switch";
306 return "lock";
308 return "climate";
310 return "fan";
312 return "water_heater";
313 // No platform drives these classes; listed so -Wswitch flags a new class that needs a decision.
317 return nullptr;
318 }
319 return nullptr;
320}
321
322bool device_is_experimental(DeviceType type, uint8_t subtype) {
323 const DeviceTypeInfo *row = find_exact_device_type_info(type, subtype);
324 return row != nullptr && (row->flags & device_type_flags::EXPERIMENTAL) != 0;
325}
326
328 const DeviceTypeInfo *row = find_device_type_info(type, subtype, DeviceModel::NONE);
329 return row != nullptr ? row->capability : DeviceCapabilityClass::UNKNOWN;
330}
331
333 if (const DeviceTypeInfo *model_row = find_device_model_info(type, subtype, model))
334 return model_row->features;
335 const DeviceTypeInfo *exact = find_exact_device_type_info(type, subtype);
336 if (exact != nullptr)
337 return exact->features;
338 const DeviceTypeInfo *type_row = find_exact_device_type_info(type, 0);
339 if (subtype == SUBTYPE_MAIN_PARAMETER_ONLY)
340 return main_parameter_only_variant(type_row != nullptr ? type_row->features : DeviceFeatures{0});
341 return type_row != nullptr ? type_row->features : DeviceFeatures{0};
342}
343
345 switch (entity_class) {
347 return COVER | VENT;
351 return BINARY_COMMAND;
353 return LEVELS | EXTENDED_POSITION_FRAME;
354 // No EXECUTE position path; listed so -Wswitch flags a new class that needs a decision. A water
355 // heater must declare its kind (water_heater.py requires io_device_type), so it is never undeclared.
361 return 0;
362 }
363 return 0;
364}
365
366std::string format_device_kind(DeviceType type, uint8_t subtype) {
367 const DeviceTypeInfo *type_row = find_exact_device_type_info(type, 0);
368 if (type_row == nullptr) {
369 char buf[TYPE_HEX_BUFFER_SIZE];
370 snprintf(buf, sizeof(buf), "unknown type 0x%02X", static_cast<unsigned>(type));
371 return subtype == 0 ? std::string(buf) : std::string(buf) + ", subtype " + std::to_string(subtype);
372 }
373
374 const DeviceTypeInfo *subtype_row = subtype == 0 ? nullptr : find_exact_device_type_info(type, subtype);
375 if (subtype_row != nullptr && (subtype_row->flags & STANDALONE_NAME) != 0)
376 return subtype_row->name;
377
378 // Identifiers are lowercase words joined by underscores; the display form swaps in spaces.
379 std::string kind = type_row->name;
380 for (char &c : kind) {
381 if (c == '_')
382 c = ' ';
383 }
384 if (subtype == 0)
385 return kind;
386 return kind + ", " + (subtype_row != nullptr ? std::string(subtype_row->name) : "subtype " + std::to_string(subtype));
387}
388
389std::string format_device_ids(DeviceType type, uint8_t subtype) {
390 char buf[TYPE_HEX_BUFFER_SIZE];
391 snprintf(buf, sizeof(buf), "0x%02X/%u", static_cast<unsigned>(type), static_cast<unsigned>(subtype));
392 return buf;
393}
394
395} // namespace home_io_control
396} // namespace esphome
DeviceFeatures device_features(DeviceType type, uint8_t subtype, DeviceModel model)
Capability bits of a (type, subtype, model).
DeviceFeatures undeclared_kind_features(DeviceCapabilityClass entity_class)
Features of a device kind that has no table row, or that is not declared at all.
std::string format_device_ids(DeviceType type, uint8_t subtype)
Wire values of a (type, subtype), for logs that must stay readable without the table.
DeviceCapabilityClass device_capability_class(DeviceType type)
Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
@ PEDESTRIAN
Move to the pedestrian position (gate openers); experimental.
@ PARTIAL
Move to the stored partial position (garage openers; issue #163).
const DeviceTypeInfo * find_device_type_info(DeviceType type, uint8_t subtype, DeviceModel model)
Look up the table row for a (type, subtype, model).
DeviceModel
A product model below a (node type, subtype): for products that report the same wire type but need di...
@ HEATER_WITH_LIGHT
Heater with a night light under the electrical heater with an adjustable setpoint (52/1).
@ NONE
No model: the (type, subtype) row.
@ TOWEL_DRYER
Towel dryer under the electrical heater with an adjustable setpoint (52/1).
const DeviceTypeInfo * find_device_model_info(DeviceType type, uint8_t subtype, DeviceModel model)
Look up the model row with exactly this (type, subtype, model), without falling back.
DeviceType
IO-Homecontrol node types.
@ EXTERIOR_VENETIAN_BLIND
Exterior venetian blind.
@ VERTICAL_INTERIOR_BLIND
Vertical interior blind.
@ CENTRAL_HOUSE_CONTROL
Central house control.
@ VERTICAL_EXTERIOR_AWNING
Vertical exterior awning.
@ HEATING_TEMPERATURE_INTERFACE
Heating temperature interface.
@ BEACON
Beacon (unpaired/announcement).
@ SECURE_CONFIGURATION_DEVICE
Secure configuration device (a commissioning tool, not an actuator).
@ INTERIOR_VENETIAN_BLIND
Interior venetian blind.
@ UNKNOWN
Unknown/unspecified device.
@ WINDOW_OPENER
Window opening actuator.
@ HEAT_RECOVERY_VENTILATION
Heat recovery ventilation.
@ HORIZONTAL_AWNING
Horizontal awning (open/close inverted).
@ TEST_AND_EVALUATION
Test and evaluation equipment.
@ DUAL_ROLLER_SHUTTER
Dual-section roller shutter.
@ REMOTE_CONTROLLER
Remote controller (all ten type bits set, packed bytes FF C0).
@ BIOCLIMATIC_PERGOLA
Bioclimatic pergola (louvre opening as the position).
const DeviceTypeInfo * find_exact_device_type_info(DeviceType type, uint8_t subtype)
Look up the table row with exactly this (type, subtype), without falling back.
bool device_offers_models(DeviceType type, uint8_t subtype)
Does this exact (type, subtype) have model rows below it?
constexpr DeviceFeatures main_parameter_only_variant(DeviceFeatures type_features)
The features of a subtype-58 variant of a kind.
const char * device_platform_name(DeviceType type, uint8_t subtype)
Name of the ESPHome platform that drives this (type, subtype).
bool device_has_platform(DeviceType type, uint8_t subtype)
Can an ESPHome platform drive this (type, subtype) today?
bool device_is_experimental(DeviceType type, uint8_t subtype)
Is this (type, subtype) a kind no capture or report has confirmed on a real device?
uint16_t DeviceFeatures
Capability bits of a device kind; see device_feature.
DeviceCapabilityClass
High‑level capability class derived from DeviceType.
@ CLIMATE
Climate device (heating/cooling).
@ HEATER_LEVEL
Heater with a heat level in the position field (exterior heating).
@ COVER
Position‑controlled cover (shutter/blind/awning).
@ WATER_HEATER
Domestic hot water heater (device_feature::WATER_HEATER).
std::string format_device_kind(DeviceType type, uint8_t subtype)
Human-readable description of a (type, subtype) for logs and reports.
The IO node-type and subtype table: names, entity class, platform and behaviour flags.
One row of the node-type / subtype table.
uint8_t flags
device_type_flags bits.
uint8_t subtype
Subtype; 0 is the type's own row.
DeviceFeatures features
device_feature bits: the whole word, never merged with the type row's.
const char * name
Type row: lowercase identifier ("roller_shutter").
DeviceModel model
NONE for a type or subtype row; the model of a model row.
DeviceCapabilityClass capability
Entity family the device maps to.