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 <cstdio>
8
9namespace esphome {
10namespace home_io_control {
11
12namespace {
13
14using device_type_flags::BRIGHTNESS_ONLY;
15using device_type_flags::HAS_PLATFORM;
16using device_type_flags::MAIN_PARAMETER_ONLY;
17using device_type_flags::STANDALONE_NAME;
18using device_type_flags::YAML_SELECTABLE;
19
27
28constexpr uint8_t SELECTABLE_PLATFORM =
29 YAML_SELECTABLE | HAS_PLATFORM; ///< Type row that YAML can name and a platform drives.
30constexpr uint8_t SUBTYPE_PLATFORM = HAS_PLATFORM; ///< Subtype row a platform drives.
31
32constexpr size_t TYPE_HEX_BUFFER_SIZE = 24; ///< "unknown type 0x3FF" plus margin.
33
34/// @brief The table, sorted by (type, subtype).
35///
36/// A row without HAS_PLATFORM is a type the project recognises and names but cannot drive yet:
37/// discovery and the roll-call scan report it as "not supported yet" and print no YAML.
38/// The capability class is the entity family the device would map to, so a type with a class but no
39/// platform (ventilation point, heating interface) is a statement about the device, not a promise.
40///
41/// Rows that no capture or report has confirmed yet are experimental; the 6/1 and 6/2 light rows are
42/// unproven on hardware: \todo Confirm 6/1 and 6/2 accept the brightness EXECUTE on a real device.
43/// \todo Confirm the 15/2, 9/1, 9/2, 22/x, 24/58, 27/1 and 1023/2..5 subtype rows against a capture.
44constexpr DeviceTypeInfo DEVICE_TYPE_TABLE[] = {
45 {DeviceType::UNKNOWN, 0, "unknown", CAP_NONE, YAML_SELECTABLE},
46
47 {DeviceType::INTERIOR_VENETIAN_BLIND, 0, "interior_venetian_blind", CAP_COVER, SELECTABLE_PLATFORM},
48
49 {DeviceType::ROLLER_SHUTTER, 0, "roller_shutter", CAP_COVER, SELECTABLE_PLATFORM},
50 {DeviceType::ROLLER_SHUTTER, 1, "with adjustable slats", CAP_COVER, SUBTYPE_PLATFORM},
51 {DeviceType::ROLLER_SHUTTER, 2, "with projection", CAP_COVER, SUBTYPE_PLATFORM},
52
53 {DeviceType::VERTICAL_EXTERIOR_AWNING, 0, "vertical_exterior_awning", CAP_COVER, SELECTABLE_PLATFORM},
54
55 {DeviceType::WINDOW_OPENER, 0, "window_opener", CAP_COVER, SELECTABLE_PLATFORM},
56 {DeviceType::WINDOW_OPENER, 1, "with integrated rain sensor", CAP_COVER, SUBTYPE_PLATFORM},
57
58 // Every garage opener takes the main parameter only (issue #163); subtype 58 does so on any type.
59 {DeviceType::GARAGE_OPENER, 0, "garage_opener", CAP_COVER, SELECTABLE_PLATFORM | MAIN_PARAMETER_ONLY},
60 {DeviceType::GARAGE_OPENER, SUBTYPE_MAIN_PARAMETER_ONLY, "position only", CAP_COVER, SUBTYPE_PLATFORM},
61
62 {DeviceType::LIGHT, 0, "light", CAP_LIGHT, SELECTABLE_PLATFORM},
63 {DeviceType::LIGHT, 1, "dimmable RGB", CAP_LIGHT, SUBTYPE_PLATFORM | BRIGHTNESS_ONLY},
64 {DeviceType::LIGHT, 2, "tunable white", CAP_LIGHT, SUBTYPE_PLATFORM | BRIGHTNESS_ONLY},
65 {DeviceType::LIGHT, SUBTYPE_MAIN_PARAMETER_ONLY, "on/off only", CAP_LIGHT, SUBTYPE_PLATFORM},
66
67 {DeviceType::GATE_OPENER, 0, "gate_opener", CAP_COVER, SELECTABLE_PLATFORM},
68 {DeviceType::GATE_OPENER, SUBTYPE_MAIN_PARAMETER_ONLY, "position only", CAP_COVER, SUBTYPE_PLATFORM},
69
70 {DeviceType::ROLLING_DOOR_OPENER, 0, "rolling_door_opener", CAP_COVER, SELECTABLE_PLATFORM},
71
72 {DeviceType::LOCK, 0, "lock", CAP_LOCK, SELECTABLE_PLATFORM},
73 {DeviceType::LOCK, 1, "window lock", CAP_LOCK, SUBTYPE_PLATFORM | STANDALONE_NAME},
74 {DeviceType::LOCK, 2, "car controller", CAP_NONE, STANDALONE_NAME},
75
76 {DeviceType::VERTICAL_INTERIOR_BLIND, 0, "vertical_interior_blind", CAP_COVER, SELECTABLE_PLATFORM},
77
78 {DeviceType::SECURE_CONFIGURATION_DEVICE, 0, "secure_configuration_device", CAP_NONE, 0},
79
81
82 {DeviceType::DUAL_ROLLER_SHUTTER, 0, "dual_roller_shutter", CAP_COVER, SELECTABLE_PLATFORM},
83
84 {DeviceType::HEATING_TEMPERATURE_INTERFACE, 0, "heating_temperature_interface", CAP_CLIMATE, SELECTABLE_PLATFORM},
85
86 {DeviceType::ON_OFF_SWITCH, 0, "on_off_switch", CAP_SWITCH, SELECTABLE_PLATFORM},
87 {DeviceType::ON_OFF_SWITCH, 2, "micromodule", CAP_SWITCH, SUBTYPE_PLATFORM},
88
89 {DeviceType::HORIZONTAL_AWNING, 0, "horizontal_awning", CAP_COVER, SELECTABLE_PLATFORM},
90 {DeviceType::HORIZONTAL_AWNING, 1, "rail-guided pergola", CAP_COVER, SUBTYPE_PLATFORM},
91
92 {DeviceType::EXTERIOR_VENETIAN_BLIND, 0, "exterior_venetian_blind", CAP_COVER, SELECTABLE_PLATFORM},
93
94 {DeviceType::LOUVRE_BLIND, 0, "louvre_blind", CAP_COVER, SELECTABLE_PLATFORM},
95
96 {DeviceType::CURTAIN_TRACK, 0, "curtain_track", CAP_COVER, SELECTABLE_PLATFORM},
97
98 {DeviceType::VENTILATION_POINT, 0, "ventilation_point", CAP_SWITCH, 0},
99 {DeviceType::VENTILATION_POINT, 1, "air inlet", CAP_SWITCH, 0},
100 {DeviceType::VENTILATION_POINT, 2, "air transfer", CAP_SWITCH, 0},
101 {DeviceType::VENTILATION_POINT, 3, "air outlet", CAP_SWITCH, 0},
102
103 // Exterior heating takes a heat level in the position field (a fan entity), not a setpoint. Every
104 // exterior heater gets the main parameter only: its first functional parameter is not a cover's, and
105 // a guessed block is what issue #163 showed to cost a refusal.
106 {DeviceType::EXTERIOR_HEATING, 0, "exterior_heating", CAP_HEATER_LEVEL, SELECTABLE_PLATFORM | MAIN_PARAMETER_ONLY},
107 {DeviceType::EXTERIOR_HEATING, SUBTYPE_MAIN_PARAMETER_ONLY, "on/off only", CAP_HEATER_LEVEL, SUBTYPE_PLATFORM},
108
109 {DeviceType::HEAT_PUMP, 0, "heat_pump", CAP_CLIMATE, SELECTABLE_PLATFORM},
110 // The two Atlantic profiles use a different private register family from the electrical
111 // heater's, so the heating payloads must not be sent to them: no climate class.
112 {DeviceType::HEAT_PUMP, 1, "heat pump (Atlantic)", CAP_NONE, STANDALONE_NAME},
113 {DeviceType::HEAT_PUMP, 2, "boiler (Atlantic)", CAP_NONE, STANDALONE_NAME},
114
115 {DeviceType::INTRUSION_ALARM, 0, "intrusion_alarm", DeviceCapabilityClass::SENSOR, YAML_SELECTABLE},
116
117 {DeviceType::SWINGING_SHUTTER, 0, "swinging_shutter", CAP_COVER, SELECTABLE_PLATFORM},
118 {DeviceType::SWINGING_SHUTTER, 1, "with independent leaves", CAP_COVER, SUBTYPE_PLATFORM},
119 {DeviceType::SWINGING_SHUTTER, SUBTYPE_MAIN_PARAMETER_ONLY, "position only", CAP_COVER, SUBTYPE_PLATFORM},
120
121 {DeviceType::SLIDING_WINDOW, 0, "sliding_window", CAP_COVER, SELECTABLE_PLATFORM},
122 {DeviceType::SLIDING_WINDOW, 1, "with lock", CAP_COVER, SUBTYPE_PLATFORM},
123
124 {DeviceType::ZONE_CONTROL, 0, "zone_control", CAP_NONE, 0},
125
126 {DeviceType::BIOCLIMATIC_PERGOLA, 0, "bioclimatic_pergola", CAP_COVER, SELECTABLE_PLATFORM},
127
128 {DeviceType::SIREN, 0, "siren", CAP_NONE, 0},
129
130 {DeviceType::DOMESTIC_HOT_WATER, 0, "domestic_hot_water", CAP_NONE, 0},
131
132 // Both rows have a heating profile (proto_heating_profiles.cpp): the plain heater is commanded with
133 // heating levels in the standard EXECUTE, the one with an adjustable setpoint over the private
134 // registers (proto_heating.h).
135 {DeviceType::ELECTRICAL_HEATER, 0, "electrical_heater", CAP_CLIMATE, SELECTABLE_PLATFORM},
136 {DeviceType::ELECTRICAL_HEATER, 1, "with adjustable setpoint", CAP_CLIMATE, SUBTYPE_PLATFORM},
137
138 {DeviceType::HEAT_RECOVERY_VENTILATION, 0, "heat_recovery_ventilation", CAP_NONE, 0},
139
140 {DeviceType::CENTRAL_HOUSE_CONTROL, 0, "central_house_control", CAP_NONE, 0},
141
142 {DeviceType::TEST_AND_EVALUATION, 0, "test_and_evaluation", CAP_NONE, 0},
143
144 {DeviceType::REMOTE_CONTROLLER, 0, "remote_controller", CAP_NONE, 0},
145 {DeviceType::REMOTE_CONTROLLER, 2, "Keygo", CAP_NONE, 0},
146 {DeviceType::REMOTE_CONTROLLER, 3, "Izymo", CAP_NONE, 0},
147 {DeviceType::REMOTE_CONTROLLER, 4, "public frame", CAP_NONE, 0},
148 {DeviceType::REMOTE_CONTROLLER, 5, "V500", CAP_NONE, 0},
149};
150
151} // namespace
152
154 for (const DeviceTypeInfo &row : DEVICE_TYPE_TABLE) {
155 if (row.type == type && row.subtype == subtype)
156 return &row;
157 }
158 return nullptr;
159}
160
161const DeviceTypeInfo *find_device_type_info(DeviceType type, uint8_t subtype) {
162 const DeviceTypeInfo *row = find_exact_device_type_info(type, subtype);
163 return row != nullptr || subtype == 0 ? row : find_exact_device_type_info(type, 0);
164}
165
166bool device_has_platform(DeviceType type, uint8_t subtype) {
167 const DeviceTypeInfo *row = find_device_type_info(type, subtype);
168 return row != nullptr && (row->flags & HAS_PLATFORM) != 0;
169}
170
171const char *device_platform_name(DeviceType type, uint8_t subtype) {
172 if (!device_has_platform(type, subtype))
173 return nullptr;
174 switch (find_device_type_info(type, subtype)->capability) {
176 return "cover";
178 return "light";
180 return "switch";
182 return "lock";
184 return "climate";
186 return "fan";
187 // No platform drives these classes; listed so -Wswitch flags a new class that needs a decision.
191 return nullptr;
192 }
193 return nullptr;
194}
195
197 const DeviceTypeInfo *row = find_device_type_info(type, subtype);
198 return row != nullptr ? row->capability : DeviceCapabilityClass::UNKNOWN;
199}
200
201bool device_light_is_brightness_only(DeviceType type, uint8_t subtype) {
202 const DeviceTypeInfo *row = find_device_type_info(type, subtype);
203 return row != nullptr && (row->flags & BRIGHTNESS_ONLY) != 0;
204}
205
206std::string format_device_kind(DeviceType type, uint8_t subtype) {
207 const DeviceTypeInfo *type_row = find_exact_device_type_info(type, 0);
208 if (type_row == nullptr) {
209 char buf[TYPE_HEX_BUFFER_SIZE];
210 snprintf(buf, sizeof(buf), "unknown type 0x%02X", static_cast<unsigned>(type));
211 return subtype == 0 ? std::string(buf) : std::string(buf) + ", subtype " + std::to_string(subtype);
212 }
213
214 const DeviceTypeInfo *subtype_row = subtype == 0 ? nullptr : find_exact_device_type_info(type, subtype);
215 if (subtype_row != nullptr && (subtype_row->flags & STANDALONE_NAME) != 0)
216 return subtype_row->name;
217
218 // Identifiers are lowercase words joined by underscores; the display form swaps in spaces.
219 std::string kind = type_row->name;
220 for (char &c : kind) {
221 if (c == '_')
222 c = ' ';
223 }
224 if (subtype == 0)
225 return kind;
226 return kind + ", " + (subtype_row != nullptr ? std::string(subtype_row->name) : "subtype " + std::to_string(subtype));
227}
228
229std::string format_device_ids(DeviceType type, uint8_t subtype) {
230 char buf[TYPE_HEX_BUFFER_SIZE];
231 snprintf(buf, sizeof(buf), "0x%02X/%u", static_cast<unsigned>(type), static_cast<unsigned>(subtype));
232 return buf;
233}
234
235} // namespace home_io_control
236} // namespace esphome
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.
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.
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?
const DeviceTypeInfo * find_device_type_info(DeviceType type, uint8_t subtype)
Look up the table row for a (type, subtype) pair.
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).
std::string format_device_kind(DeviceType type, uint8_t subtype)
Human-readable description of a (type, subtype) for logs and reports.
bool device_light_is_brightness_only(DeviceType type, uint8_t subtype)
Is this light limited to brightness (no colour, no colour temperature)?
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.
const char * name
Type row: lowercase identifier ("roller_shutter").
DeviceCapabilityClass capability
Entity family the device maps to.