Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
management_actions.h
Go to the documentation of this file.
1#pragma once
2
3/// @file management_actions.h
4/// @brief Hub-level management operations exposed as Home Assistant actions.
5/// @ingroup hioc_hub
6///
7/// ManagementActions owns the family of advanced, non-entity hub actions (device rename,
8/// identify, force-open, ...) and the native API registration that wires them into Home
9/// Assistant. Every action is registered through one data-driven API service descriptor
10/// (detail::ManagementServiceDescriptor); adding a new action does not require a new
11/// descriptor class. The descriptor holds a ManagementActions* and calls only public
12/// methods on it, so no friend declarations into the hub are needed.
13///
14/// IOHomeControlComponent exposes this collaborator through thin protected wrappers
15/// (api_rename_device_, api_identify_device_, api_force_open_device_, api_scan_paired_devices_,
16/// register_management_actions_).
17
18#include "exchange_engine.h"
19#include "device_registry.h"
20
21#include <cstdint>
22#include <string>
23
24namespace esphome {
25namespace home_io_control {
26
27// Forward declarations — full definitions included only in management_actions.cpp.
29struct TuningConfig;
30
31/// Result of a hub-level management action such as rename.
32///
33/// Returned by ManagementActions::rename_device() and re-exported from the hub
34/// as IOHomeControlComponent::ManagementActionResult via a public type alias.
36 bool success{false}; ///< Whether the requested management action succeeded.
37 bool verified{false}; ///< Whether a follow-up readback confirmed the applied state.
38 bool has_result_code{false}; ///< True when result_code contains a decoded CMD_ERROR_RESP byte.
39 uint8_t result_code{0}; ///< Optional CMD_ERROR_RESP result byte.
40 std::string action; ///< Action name, e.g. "rename_device".
41 std::string device_id; ///< Target IO-homecontrol device ID.
42 std::string message; ///< Human-readable outcome summary.
43 std::string requested_name; ///< Requested normalized UTF-8 name for rename actions.
44 std::string applied_name; ///< Verified cached UTF-8 name after a readback, when available.
45 std::string probe_name; ///< Probe name for probe_device()/probe_sweep(), e.g. "private_fn".
46 std::string probe_index; ///< Requested index argument, as received (before parsing).
47 bool has_response_cmd{false}; ///< True when response_cmd contains a probe reply's command byte.
48 uint8_t response_cmd{0}; ///< Probe reply's command byte (distinct from a CMD_ERROR_RESP
49 ///< result_code above, which is a different byte with a
50 ///< different meaning).
51 std::string response_hex; ///< Probe reply's full raw wire hex, for pasting into
52 ///< scripts/corpus/ingest.py.
53 bool terminal_refusal{false}; ///< True when probe_device() failed for a reason that will
54 ///< recur identically for every remaining index in a sweep
55 ///< (diagnostic probes not enabled, device moving) — set only by
56 ///< probe_device(), read only by probe_sweep() to decide
57 ///< whether to stop early. A structured flag rather than
58 ///< probe_sweep() pattern-matching `message` text, which would
59 ///< silently break if that text were ever reworded.
60};
61
62/// Encapsulates hub-level management operations exposed as Home Assistant actions.
63///
64/// ManagementActions is constructed once by IOHomeControlComponent. It stores pointers
65/// and references into hub members; it is non-copyable for the same reason as ExchangeEngine.
66/// @ingroup hioc_hub
68 public:
69 /// Construct with all required collaborators.
70 ///
71 /// @param node_id Controller 3-byte node ID, owned by the hub.
72 /// @param system_key Controller 16-byte AES system key, owned by the hub. Only
73 /// scan_paired_devices() uses this — it builds a self-authenticating
74 /// CMD_DISCOVER_SPE_REQ itself, unlike rename/identify/force-open, which
75 /// authenticate through ExchangeEngine's challenge-response.
76 /// @param tuning Runtime tuning config, owned by the hub. Only scan_paired_devices()
77 /// uses this — it reuses `pairing_discovery_wait_ms` as the listen window
78 /// for each of its own per-channel attempts.
79 /// @param engine Shared exchange engine for radio transactions.
80 /// @param registry Device registry for device lookups.
81 /// @param initialized Pointer to the hub's initialized flag.
82 /// @param hub Hub pointer for ESPHome API calls and public hub methods.
83 ManagementActions(const uint8_t *node_id, const uint8_t *system_key, const TuningConfig *tuning,
84 ExchangeEngine &engine, DeviceRegistry &registry, const bool *initialized,
86
87 /// Non-copyable — stores references and pointers into hub member addresses.
90
91 /// Register all management actions (rename, identify, force-open, ...) with ESPHome's
92 /// native API server.
93 void register_actions();
94
95 /// Native API callback: rename a device and publish the result as a HA event.
96 void api_rename_device(const std::string &device_id, const std::string &new_name);
97
98 /// Rename a registered device and verify the result by reading the name back.
99 /// @param device_id Target device ID (hex string, case-insensitive).
100 /// @param new_name Requested UTF-8 device name.
101 /// @return Structured result describing success, verification, and any error.
102 ManagementActionResult rename_device(const std::string &device_id, const std::string &new_name);
103
104 /// Native API callback: trigger a device's physical identify and publish the result as a HA event.
105 void api_identify_device(const std::string &device_id);
106
107 /// @brief Trigger a registered device's physical identify (brief jog/flash).
108 ///
109 /// Deliberately performs no device-type gating beyond "is it registered on this hub" — identify
110 /// exists precisely to let a user work out what an unknown registry entry physically is, so
111 /// restricting it to specific device types would defeat the purpose. A CMD_ERROR_RESP reply is
112 /// treated as a successful trigger (not a failure): per the protocol, devices may answer an
113 /// identify request that way and still perform the jog.
114 /// @param device_id Target device ID (hex string, case-insensitive).
115 /// @return Structured result describing success and any validation failure. `verified` is
116 /// always false — there is no readback that confirms a physical jog happened.
117 ManagementActionResult identify_device(const std::string &device_id);
118
119 /// Native API callback: force-open a device and publish the result as a HA event.
120 void api_force_open_device(const std::string &device_id);
121
122 /// @brief Move a registered cover device to fully open at elevated priority, intended to
123 /// bypass wind/rain soft locks.
124 ///
125 /// Safety-sensitive: this deliberately requires an explicit `device_id` (a hub-level action,
126 /// not a permanent per-device button) because it can override legitimate environmental
127 /// protection. Queues the command onto the hub's normal cover-command dispatch path
128 /// (IOHomeControlComponent::queue_device_command()), which already owns capability gating,
129 /// poll tracking, and backoff — this method does not talk to the radio directly. The result
130 /// therefore only confirms that the command was queued; the actual movement outcome arrives
131 /// later via the device's normal cover-state/polling pipeline, so `verified` is always false.
132 /// @note The elevated-priority mechanism (see create_force_open() in proto_commands.cpp) is
133 /// confirmed on real hardware to correctly move a device to fully open, but bypassing
134 /// an *active* environmental lock is still experimental — only that the device accepts
135 /// the frame and opens when nothing is locking it has been observed so far.
136 /// @param device_id Target device ID (hex string, case-insensitive).
137 /// @return Structured result describing whether the command was queued.
138 ManagementActionResult force_open_device(const std::string &device_id);
139
140 /// Native API callback: run a roll-call scan and publish the result as a HA event.
142
143 /// @brief Native API callback: queue a 1W position for a controller identity.
144 ///
145 /// An action rather than an entity per percentage (ADR 0006). `position` arrives as a string
146 /// because every argument on this action surface is a string; it is parsed and range-checked
147 /// here, and a bad value is reported rather than coerced — a malformed position that silently
148 /// became 0 would send a fully-open command to a whole device class.
149 ///
150 /// The published result says only that the command was *queued*. 1W has no reply, so nothing
151 /// downstream can ever upgrade that to "the device moved"; the per-identity "Last 1W Command"
152 /// sensor reports what was transmitted.
153 /// @param controller_id Controller-identity handle from `oneway_controllers:`.
154 /// @param position Target position 0-100 as a decimal string.
155 void api_oneway_set_position(const std::string &controller_id, const std::string &position);
156
157 /// @brief Native API callback: queue a standalone 1W un-enrollment (remove-controller, CMD
158 /// 0x39) for a controller identity.
159 ///
160 /// The direct caller of `0x39` in this codebase (ADR 0006 — a rare rollback path gets an
161 /// action, not a permanent entity) — the "Enroll" button also fires this same frame, but only
162 /// as the documented prelude immediately before its own `0x30`, never on its own. Same "queued,
163 /// not confirmed" framing as api_oneway_set_position() — 1W has no reply, so nothing here can
164 /// ever say a device actually forgot this identity.
165 ///
166 /// @warning **Unconfirmed standalone on real hardware.** An enrolled device has been observed
167 /// to ignore this frame sent alone — the hub kept controlling it afterwards. The frame itself
168 /// matches every real captured `0x39` this project holds (payload shape, MAC span), so the most
169 /// likely explanation is the same physical gate enrollment has: a device may only act on `0x39`
170 /// while its receiver is in the 2 s PROG association-mode window, and the failed attempt was
171 /// fired without it — untested, not ruled out. Do not treat this action as a confirmed rollback
172 /// until it is retested with that gesture.
173 /// @param controller_id Controller-identity handle from `oneway_controllers:`.
174 void api_oneway_remove_controller(const std::string &controller_id);
175
176 /// @brief Broadcast a roll-call and report every device that answers.
177 ///
178 /// Not a discovery mechanism for new devices: only devices that already hold this hub's
179 /// system key answer a CMD_DISCOVER_SPE_REQ (the 0x2A payload is self-authenticating — 6
180 /// random bytes plus a 6-byte HMAC over the command byte, computed with `system_key_`), so a
181 /// device that has never paired with this hub stays silent. See
182 /// `tests/corpus/captures/discovery/somfy_awning_discovery_spe_paired_rollcall.yaml` for a captured
183 /// exchange. Never writes DeviceRegistry — every responder is only looked up, never
184 /// registered; an unknown responder usually means a device paired earlier whose YAML config
185 /// was never saved, not an intruder. `result.device_id` stays empty (there is no single
186 /// target) and `verified` stays false (nothing here is read back). `success` is true whenever
187 /// the broadcast went out, including with zero replies — "nothing answered" is a valid
188 /// result, not a failure. `result.message` carries the full multi-line report: a header line
189 /// (devices detected, how many known vs. unknown), then a `Known:` section followed by an
190 /// `Unknown:` section (either omitted if empty) — known responders get a summary line each,
191 /// unknown responders additionally get a lead-in sentence and a ready-to-paste YAML block.
192 /// Devices are grouped by known/unknown rather than left in arrival order, since the two
193 /// groups need different follow-up and interleaving them made an unknown responder easy to
194 /// miss between known ones. At most `SCAN_MAX_REPLIES` distinct responders are reported; if
195 /// more answer, the report says so explicitly rather than quietly listing a subset.
196 ///
197 /// Transmits the request once per channel (CH2, then CH1, then CH3), each with its own full
198 /// `pairing_discovery_wait_ms` listen window, merging distinct responders across attempts — a
199 /// paired device only hears the broadcast if it happens to be awake on the channel the hub
200 /// transmits on at that instant, and real hardware testing found that single-channel
201 /// duty-cycling paired devices are not reliably caught by a one-shot broadcast. A responder
202 /// that answers more than one attempt still appears exactly once. Each attempt is a single
203 /// transmit followed by its own window, never back-to-back transmits, so it does not
204 /// reintroduce the different failure mode a 3-channel-burst transmit caused elsewhere: firing
205 /// three long-preamble transmits back-to-back with no listening in between blew through the
206 /// tight per-try response wait windows and broke exchanges in both directions (see
207 /// `KeyExtractionResponder::broadcast_reply_()`'s doc comment).
208 ///
209 /// This still blocks the caller for the full three-window duration (roughly
210 /// `3 × pairing_discovery_wait_ms`, ~6 s at the 2000 ms default) and therefore trips ESPHome's
211 /// "operation took a long time" warning on *every* invocation. That warning uses a per-component
212 /// ratchet (`Component::should_warn_of_blocking()`): it starts at 50 ms and, each time it fires,
213 /// raises its own threshold to the observed duration plus a margin — but the threshold is stored
214 /// in centiseconds in a `uint8_t`, so it saturates at **2550 ms**. Anything that blocks longer
215 /// than that can never ratchet out of warning range.
216 ///
217 /// Accepting that is a deliberate tradeoff (confirmed on real hardware 2026-08-10). A shorter
218 /// fixed window (500 ms/attempt, ~2.4 s total) was tried: it would have gone quiet after one
219 /// warning, since 2.4 s sits under the 2550 ms cap — but it also caused real, correctly-decoded
220 /// replies from registered devices to arrive after the window had already closed, where the
221 /// passive path drops them (`hub_status.cpp`'s `unhandled_cmd` catch-all). Losing devices from
222 /// the report is worse than a recurring log line, so the long window won. Getting both would
223 /// require restructuring this action to run across multiple scheduled `loop()` ticks so no single
224 /// blocking unit approaches 2550 ms — not done here.
225 /// @return Structured result whose `message` is the full report.
227
228 /// Native API callback: run a single diagnostic probe and publish the result as a HA event.
229 void api_probe_device(const std::string &device_id, const std::string &probe, const std::string &index);
230
231 /// @brief Send a single diagnostic probe frame to an already-paired device and report the raw
232 /// reply.
233 ///
234 /// Protocol-research instrumentation for opcodes this codebase has not decoded — see
235 /// docs/radio_diagnostics.md and ADR 0024. Refuses unless
236 /// this build was configured with `diagnostic_probes: true`
237 /// (IOHomeControlComponent::diagnostic_probes_enabled()), and separately refuses while the
238 /// target device is moving (`!effective_is_stopped(*dev)`) — an unknown frame into a
239 /// mid-transaction device state machine is the one avoidable way a read-shaped probe could
240 /// cause harm. That is the device's last *reported* movement state *or* a standing hub
241 /// prediction that it is moving (a command the hub just issued, not yet confirmed): both
242 /// warrant the same refusal. It is always "stopped" (never refuses) for a light/switch, and the
243 /// reported state can be stale if the device was last moved from a physical remote the hub
244 /// never saw. Routes
245 /// the response straight into `result.message` as raw hex; it
246 /// is never passed to the hub's status decoder (`update_device_status_()` is not reachable
247 /// from this class at all — see hub_core.h), so a probe can never be misread as a position
248 /// update. `verified` is always false: the whole point is that the reply's meaning is not yet
249 /// known.
250 ///
251 /// `probe` selects the frame builder:
252 /// - "private_fn": create_private_function(), `index` = function ID (Q0).
253 /// - "private_fn_sub": create_private_function() at function ID 0x09, `index` = the second
254 /// payload byte (data[1]) — an undecoded parameter-addressing byte no project has ever
255 /// sent as non-zero.
256 /// - "status_ext": create_get_status_extended() at the field-observed selector 0x80,
257 /// `index` = block/N (Q1). The selector-0x20 tilt control is create_get_status_tilt()
258 /// (already shipped) and is not part of this probe.
259 /// - "status_ext_fn6" / "status_ext_fn9": create_get_status_extended() at selector 0x80 with
260 /// function ID 0x06 / 0x09 instead of the usual 0x03, `index` = block/N. The 4-byte shape
261 /// is field-observed; the function ID in it is not.
262 /// - "get_info1": create_get_info1() (CMD_GET_INFO1 0x54, no payload), `index` ignored.
263 /// - "get_info2": create_get_info2() (CMD_GET_INFO2 0x56, no payload), `index` ignored.
264 /// - "general_info3": create_general_info3(), `index` ignored (Q2).
265 /// - "private2": create_private2_read() long form, `index` = modifier (Q3).
266 /// - "private2_short": create_private2_read() short form, `index` = modifier (Q3).
267 /// There is deliberately no "unknown4a" probe and no builder for CMD_UNKNOWN4A_REQ (0x4A)
268 /// anywhere in this codebase — see ADR 0024 for the standing safety decision.
269 ///
270 /// `index` is a string, not an int, because ManagementServiceDescriptor::
271 /// encode_list_service_response() hardcodes every native-API action argument as
272 /// SERVICE_ARG_TYPE_STRING (see the .cpp) — parsed here via parse_probe_index(), which
273 /// accepts both "6" and "0x06".
274 /// @param device_id Target device ID (hex string, case-insensitive).
275 /// @param probe One of the probe names above.
276 /// @param index Function ID / selector block / modifier, depending on `probe`; a decimal or
277 /// `0x`-prefixed hex string. Ignored for "general_info3".
278 /// @return Structured result whose `message` carries the attempt count, and on a reply, the
279 /// response command byte and full raw hex.
280 ManagementActionResult probe_device(const std::string &device_id, const std::string &probe, const std::string &index);
281
282 /// Native API callback: run a bounded probe sweep and publish the result as a HA event.
283 void api_probe_sweep(const std::string &device_id, const std::string &probe, const std::string &first_index,
284 const std::string &last_index);
285
286 /// @brief Walk a bounded index range, one probe_device() call per index, in one user gesture.
287 ///
288 /// Answers Q0/Q1/Q3 (the multi-value probes) without one native-API call per value. Bounded to
289 /// PROBE_SWEEP_MAX_INDICES indices and spaced PROBE_SWEEP_DELAY_MS apart — this transmits on a
290 /// shared ISM band to what may be a battery device, so a tight loop is antisocial and would
291 /// also keep the device awake and skew results. Each index costs up to
292 /// EXCHANGE_RETRY_COUNT frames (proto_timing.h), same as a single probe_device() call.
293 ///
294 /// @note This blocks the ESPHome loop (API, other components, OTA) for the whole sweep, same
295 /// blocking-exchange model as every other management action (ADR 0013) — but a full
296 /// sweep multiplies it: worst case (every index silent) is roughly
297 /// `PROBE_SWEEP_MAX_INDICES * (EXCHANGE_RETRY_COUNT * exchange_start_response_wait_ms +
298 /// (EXCHANGE_RETRY_COUNT-1) * EXCHANGE_RETRY_DELAY_MS + PROBE_SWEEP_DELAY_MS)`, on the
299 /// order of a minute at default tuning and longer if `exchange_start_response_wait_ms`
300 /// is raised. Accepted deliberately for this maintainer-triggered, explicitly-opted-in
301 /// diagnostic (`diagnostic_probes: true`) rather than restructured into scheduled steps — see
302 /// docs/radio_diagnostics.md's "Diagnostic probes" section, which states this to the
303 /// user rather than leaving it to be discovered as a frozen dashboard.
304 /// @param device_id Target device ID (hex string, case-insensitive).
305 /// @param probe One of probe_device()'s probe names.
306 /// @param first_index First index in the sweep (inclusive), same string format as probe_device().
307 /// @param last_index Last index in the sweep (inclusive).
308 /// @return Structured result whose `message` is one line per index: answered / error-coded /
309 /// silent / refused.
310 ManagementActionResult probe_sweep(const std::string &device_id, const std::string &probe,
311 const std::string &first_index, const std::string &last_index);
312
313 /// Native API callback: run a heating/climate function and publish the result as a HA event.
314 void api_heating_control(const std::string &device_id, const std::string &function, const std::string &value);
315
316 /// @brief Send one 2W heating/climate function (CMD_WRITE_PRIVATE 0x20) to a registered climate
317 /// device.
318 ///
319 /// A real user feature, but an unvalidated one — the protocol is derived from the iohomecontrol
320 /// project's Cozytouch support and has never been exercised against real Atlantic/Thermor/Sauter
321 /// hardware (see docs/home_io_control.md's experimental banner). It is NOT diagnostic-gated;
322 /// the guard is documentation, not `diagnostic_probes:`.
323 ///
324 /// `function` is one of `power_on` / `set_temperature` / `set_mode` / `set_presence` /
325 /// `set_window` / `midnight_sync`. `value` is parsed per function: a float in degrees Celsius
326 /// (7.0-28.0) for `set_temperature`, `auto|manual|prog|off` for `set_mode`, `on|off` for
327 /// `set_presence`, `open|close` for `set_window`, ignored for the other two. A malformed value
328 /// is reported, never coerced.
329 ///
330 /// Delegates to IOHomeControlComponent::send_heating_command() so the entity and the action
331 /// share exactly one transmit path (see that method). `verified` is always false: the `set_*`
332 /// functions are write-only — nothing decodes what the radiator did into an entity. (`power_on`
333 /// and `midnight_sync` are register reads; their ACK payload is logged at DEBUG but not decoded.)
334 /// @param device_id Target device ID (hex string, case-insensitive).
335 /// @param function Heating function name (see above).
336 /// @param value Function-specific value string (see above).
337 /// @return Structured result describing success and any validation/exchange failure.
338 ManagementActionResult heating_control(const std::string &device_id, const std::string &function,
339 const std::string &value);
340
341 /// Publish a management result as one or more structured log lines (one call per line of
342 /// `result.message`, see log_multiline_result() in the .cpp) and a Home Assistant event.
343 void publish_result(const ManagementActionResult &result);
344
345 private:
346 /// @brief Shared preamble for every management action: validate the hub and device ID,
347 /// then resolve the target device.
348 ///
349 /// Initializes `result` with the action name and normalized device ID, then checks (in
350 /// order) that the hub is initialized, that `device_id` is a well-formed 6-hex-digit ID,
351 /// and that it names a device registered on this hub. On any failure, sets `result.message`
352 /// to a stable, caller-facing string and returns nullptr; callers should return `result`
353 /// immediately in that case.
354 /// @param action Action name to record on `result` (e.g. MANAGEMENT_ACTION_RENAME_DEVICE).
355 /// @param device_id Requested device ID (hex string, case-insensitive, as received from HA).
356 /// @param result Populated with the action name and normalized device ID, plus an error
357 /// message on failure.
358 /// @return Pointer to the resolved device, or nullptr if any validation step failed.
359 IoDevice *resolve_device_(const char *action, const std::string &device_id, ManagementActionResult &result);
360
361 /// @brief Send an authenticated request and report a stock timeout failure on no response.
362 ///
363 /// Shared by every action that talks to the device directly (rename, identify): both send
364 /// their built request via ExchangeEngine::send_and_receive() and, on failure, log the same
365 /// exchange debug snapshot and report the same "no valid response to <action_verb> request"
366 /// message shape. Actions that don't talk to the radio directly (force-open, which queues
367 /// through the hub's cover-command dispatch path instead) have no use for this helper.
368 /// @param request Frame to send.
369 /// @param response Populated with the device's reply on success.
370 /// @param action_verb Bare verb used only in the failure message, e.g. "rename" or "identify".
371 /// @param result Result to update with `message` on failure; `result.device_id` must already be
372 /// set (via resolve_device_()) since it is used for the debug log.
373 /// @return true if a response was received; false on timeout (result.message set, debug
374 /// snapshot logged).
375 bool send_authenticated_request_(const IoFrame &request, IoFrame &response, const char *action_verb,
376 ManagementActionResult &result);
377
378 const uint8_t *node_id_;
379 const uint8_t *system_key_;
380 const TuningConfig *tuning_;
381 ExchangeEngine &engine_;
382 DeviceRegistry &registry_;
383 const bool *initialized_;
385};
386
387} // namespace home_io_control
388} // namespace esphome
Owns the per-hub device table, update callbacks, and linked-remote associations.
The main IO-Homecontrol component.
Definition hub_core.h:90
ManagementActions(const ManagementActions &)=delete
Non-copyable — stores references and pointers into hub member addresses.
void api_probe_device(const std::string &device_id, const std::string &probe, const std::string &index)
Native API callback: run a single diagnostic probe and publish the result as a HA event.
void register_actions()
Register all management actions (rename, identify, force-open, ...) with ESPHome's native API server.
void api_rename_device(const std::string &device_id, const std::string &new_name)
Native API callback: rename a device and publish the result as a HA event.
void api_oneway_set_position(const std::string &controller_id, const std::string &position)
Native API callback: queue a 1W position for a controller identity.
ManagementActionResult scan_paired_devices()
Broadcast a roll-call and report every device that answers.
ManagementActionResult probe_device(const std::string &device_id, const std::string &probe, const std::string &index)
Send a single diagnostic probe frame to an already-paired device and report the raw reply.
ManagementActionResult rename_device(const std::string &device_id, const std::string &new_name)
Rename a registered device and verify the result by reading the name back.
void api_oneway_remove_controller(const std::string &controller_id)
Native API callback: queue a standalone 1W un-enrollment (remove-controller, CMD 0x39) for a controll...
void api_heating_control(const std::string &device_id, const std::string &function, const std::string &value)
Native API callback: run a heating/climate function and publish the result as a HA event.
void publish_result(const ManagementActionResult &result)
Publish a management result as one or more structured log lines (one call per line of result....
void api_force_open_device(const std::string &device_id)
Native API callback: force-open a device and publish the result as a HA event.
ManagementActions(const uint8_t *node_id, const uint8_t *system_key, const TuningConfig *tuning, ExchangeEngine &engine, DeviceRegistry &registry, const bool *initialized, IOHomeControlComponent *hub)
Construct with all required collaborators.
ManagementActionResult identify_device(const std::string &device_id)
Trigger a registered device's physical identify (brief jog/flash).
ManagementActionResult force_open_device(const std::string &device_id)
Move a registered cover device to fully open at elevated priority, intended to bypass wind/rain soft ...
void api_probe_sweep(const std::string &device_id, const std::string &probe, const std::string &first_index, const std::string &last_index)
Native API callback: run a bounded probe sweep and publish the result as a HA event.
void api_scan_paired_devices()
Native API callback: run a roll-call scan and publish the result as a HA event.
void api_identify_device(const std::string &device_id)
Native API callback: trigger a device's physical identify and publish the result as a HA event.
ManagementActionResult heating_control(const std::string &device_id, const std::string &function, const std::string &value)
Send one 2W heating/climate function (CMD_WRITE_PRIVATE 0x20) to a registered climate device.
ManagementActions & operator=(const ManagementActions &)=delete
ManagementActionResult probe_sweep(const std::string &device_id, const std::string &probe, const std::string &first_index, const std::string &last_index)
Walk a bounded index range, one probe_device() call per index, in one user gesture.
Per-hub device table, update-callback fan-out, and linked-remote map.
Self-contained authenticated exchange engine for IO-Homecontrol 2W.
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:88
Result of a hub-level management action such as rename.
bool verified
Whether a follow-up readback confirmed the applied state.
std::string device_id
Target IO-homecontrol device ID.
bool has_response_cmd
True when response_cmd contains a probe reply's command byte.
std::string probe_index
Requested index argument, as received (before parsing).
uint8_t result_code
Optional CMD_ERROR_RESP result byte.
std::string action
Action name, e.g. "rename_device".
uint8_t response_cmd
Probe reply's command byte (distinct from a CMD_ERROR_RESP result_code above, which is a different by...
bool terminal_refusal
True when probe_device() failed for a reason that will recur identically for every remaining index in...
std::string applied_name
Verified cached UTF-8 name after a readback, when available.
std::string probe_name
Probe name for probe_device()/probe_sweep(), e.g. "private_fn".
std::string response_hex
Probe reply's full raw wire hex, for pasting into scripts/corpus/ingest.py.
bool success
Whether the requested management action succeeded.
bool has_result_code
True when result_code contains a decoded CMD_ERROR_RESP byte.
std::string message
Human-readable outcome summary.
std::string requested_name
Requested normalized UTF-8 name for rename actions.
All runtime tunable parameters for pairing and radio diagnostics.