Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
management_helpers.h
Go to the documentation of this file.
1#pragma once
2
3/// @file management_helpers.h
4/// @brief Result type and argument/result helpers shared by ManagementActions and DiagnosticProbes.
5/// @ingroup hioc_hub
6///
7/// Both classes take native-API string arguments, resolve a device from them and report through
8/// ManagementActionResult. The pieces they have in common live here, once: the result record,
9/// device-ID normalisation, the byte-hex formatter, the CMD_ERROR_RESP decoder and the device
10/// resolution preamble. Nothing here reaches the hub.
11
12#include "device_registry.h"
13#include "proto_codecs.h"
14#include "proto_constants.h"
15#include "proto_frame.h"
16
17#include <algorithm>
18#include <array>
19#include <cctype>
20#include <cstdint>
21#include <cstdio>
22#include <string>
23
24namespace esphome {
25namespace home_io_control {
26
27/// Result of a hub-level management action such as rename.
28///
29/// Returned by ManagementActions::rename_device() and DiagnosticProbes::probe_device(), and re-exported from the hub
30/// as IOHomeControlComponent::ManagementActionResult via a public type alias.
32 bool success{false}; ///< Whether the requested management action succeeded.
33 bool verified{false}; ///< Whether a follow-up readback confirmed the applied state.
34 bool has_result_code{false}; ///< True when result_code contains a decoded CMD_ERROR_RESP byte.
35 uint8_t result_code{0}; ///< Optional CMD_ERROR_RESP result byte.
36 std::string action; ///< Action name, e.g. "rename_device".
37 std::string device_id; ///< Target IO-homecontrol device ID.
38 std::string message; ///< Human-readable outcome summary. Published as the action-result event, which
39 ///< Home Assistant records: it never carries key material.
40 std::string log_message; ///< When non-empty, the device log shows this instead of `message`. For the
41 ///< one report that may print a device key (scan_paired_devices()); the key
42 ///< stays in the log and out of the event.
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 std::string decoded; ///< Rendering of the probe reply for the replies this codebase can
54 ///< decode (currently a limitation reply, labelled "assumed layout");
55 ///< empty otherwise. Reported beside the raw hex, never instead of it.
56 bool terminal_refusal{false}; ///< True when probe_device() failed for a reason that will
57 ///< recur identically for every remaining index in a sweep
58 ///< (diagnostic probes not enabled, device moving) — set only by
59 ///< probe_device(), read only by probe_sweep() to decide
60 ///< whether to stop early. A structured flag rather than
61 ///< probe_sweep() pattern-matching `message` text, which would
62 ///< silently break if that text were ever reworded.
63};
64
65// === Shared helpers ===
66
67/// Size of the scratch buffer format_hex_byte() prints into: two digits plus the terminator, with headroom.
68inline constexpr size_t RESULT_CODE_BUFFER_SIZE = 5;
69
70/// @brief Trim and uppercase a device-ID argument as received from the native API.
71inline std::string normalize_device_id_argument(const std::string &device_id) {
72 std::string normalized = trim_ascii_whitespace(device_id);
73 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
74 [](unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
75 return normalized;
76}
77
78/// @brief Format a byte as two uppercase hex digits, no prefix. Named neutrally rather than
79/// after any one caller: used for CMD_ERROR_RESP result codes, probe reply/index command bytes,
80/// and sweep index values alike -- none of those is a "result code" except the first.
81inline std::string format_hex_byte(uint8_t value) {
82 std::array<char, RESULT_CODE_BUFFER_SIZE> buffer{};
83 std::snprintf(buffer.data(), buffer.size(), "%02X", value);
84 return std::string(buffer.data());
85}
86
87/// @brief A result that carries only the action name and the target device ID.
88inline ManagementActionResult make_management_result(const std::string &action, const std::string &device_id) {
90 result.action = action;
91 result.device_id = device_id;
92 return result;
93}
94
95/// @brief Decode a CMD_ERROR_RESP frame's result code into `result`.
96///
97/// Populates has_result_code/result_code but deliberately leaves `result.message` untouched:
98/// rename and identify_device report different wording for the same decoded code, so message
99/// composition stays with each caller. On an empty error response, sets a stock message itself
100/// (there is no code to report) and returns false; callers should treat that the same way as a
101/// decoded code, just without result-code-specific wording.
102/// @param response Frame whose cmd is CMD_ERROR_RESP.
103/// @param result Result to populate.
104/// @return true if a result code was decoded, false if the response carried no data.
105inline bool apply_error_response(const IoFrame &response, ManagementActionResult &result) {
106 if (response.data_len == 0) {
107 result.message = "device returned an empty error response";
108 return false;
109 }
110 result.has_result_code = true;
111 result.result_code = response.data[0];
112 return true;
113}
114
115/// @brief Shared preamble for every device-addressed action: validate the hub and device ID,
116/// then resolve the target device.
117///
118/// Replaces `result` with the action name and normalized device ID, then checks (in order) that
119/// the hub is initialized, that `device_id` is a well-formed 6-hex-digit ID, and that it names a
120/// device registered on this hub. On any failure, sets `result.message` to a stable,
121/// caller-facing string and returns nullptr; callers should return `result` immediately.
122/// @param action Action name to record on `result`.
123/// @param device_id Requested device ID (hex string, case-insensitive, as received from HA).
124/// @param initialized The hub's initialized flag.
125/// @param registry Device registry to look the device up in.
126/// @param result Populated with the action name and normalized device ID, plus an error message
127/// on failure.
128/// @return Pointer to the resolved device, or nullptr if any validation step failed.
129inline IoDevice *resolve_management_device(const char *action, const std::string &device_id, bool initialized,
130 DeviceRegistry &registry, ManagementActionResult &result) {
131 const std::string normalized_device_id = normalize_device_id_argument(device_id);
132 result = make_management_result(action, normalized_device_id);
133
134 if (!initialized) {
135 result.message = "hub is not initialized";
136 return nullptr;
137 }
138
139 uint8_t parsed_device_id[NODE_ID_SIZE]{};
140 if (!hex_to_bytes(normalized_device_id, parsed_device_id, NODE_ID_SIZE)) {
141 result.message = "device ID must be exactly 6 hexadecimal characters";
142 return nullptr;
143 }
144
145 auto *dev = registry.get(normalized_device_id);
146 if (dev == nullptr) {
147 result.message = "device is not registered on this hub";
148 return nullptr;
149 }
150
151 return dev;
152}
153
154} // namespace home_io_control
155} // namespace esphome
Owns the per-hub device table, update callbacks, and linked-remote associations.
IoDevice * get(const std::string &device_id)
Retrieve a registered device by ID.
Per-hub device table, update-callback fan-out, and linked-remote map.
bool apply_error_response(const IoFrame &response, ManagementActionResult &result)
Decode a CMD_ERROR_RESP frame's result code into result.
std::string format_hex_byte(uint8_t value)
Format a byte as two uppercase hex digits, no prefix.
std::string trim_ascii_whitespace(const std::string &value)
Trim leading and trailing ASCII whitespace from a string.
IoDevice * resolve_management_device(const char *action, const std::string &device_id, bool initialized, DeviceRegistry &registry, ManagementActionResult &result)
Shared preamble for every device-addressed action: validate the hub and device ID,...
ManagementActionResult make_management_result(const std::string &action, const std::string &device_id)
A result that carries only the action name and the target device ID.
constexpr size_t RESULT_CODE_BUFFER_SIZE
Size of the scratch buffer format_hex_byte() prints into: two digits plus the terminator,...
std::string normalize_device_id_argument(const std::string &device_id)
Trim and uppercase a device-ID argument as received from the native API.
bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len)
Convert a hex string (e.g., "123ABC") to a byte array.
Device-name, address-classification and 1W-frame codecs.
IO-Homecontrol command IDs, result codes and protocol enumerations.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:93
uint8_t data[FRAME_MAX_DATA_SIZE]
Command parameters (0–23 bytes). Never includes mac.
Definition proto_frame.h:99
uint8_t data_len
Actual length of data.
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...
std::string log_message
When non-empty, the device log shows this instead of message.
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 decoded
Rendering of the probe reply for the replies this codebase can decode (currently a limitation reply,...
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.