Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
log_helpers.h
Go to the documentation of this file.
1#pragma once
2
3/// @file log_helpers.h
4/// @brief Hub-layer log tag and log/format helpers shared by the hub and its collaborators.
5/// @ingroup hioc_hub
6///
7/// Carries no dependency on the hub itself, so a collaborator that only needs to log can include
8/// this header instead of the hub's private helpers (make include-graph enforces that split).
9
10#include "log_frame.h"
11#include "proto_crypto.h"
12#include "proto_frame.h"
13#include "proto_sizes.h"
14#include "radio_interface.h"
15
16#include "esphome/core/log.h"
17
18#include <array>
19#include <cinttypes>
20#include <cstdint>
21#include <cstdio>
22#include <string>
23
24namespace esphome {
25namespace home_io_control {
26namespace detail {
27
28// ============================================================================
29// Shared constants
30// ============================================================================
31
32inline constexpr const char *TAG = "home_io_control"; ///< Shared log tag for hub-level messages.
33/// Log tag of the "received a frame we did not act on" lines (log_frame_issue()), separate from TAG so a
34/// user can filter them in `logger: logs:` without hiding every hub warning.
35inline constexpr const char *FRAME_TAG = "home_io_control.frame";
36
37// ============================================================================
38// Formatting helpers
39// ============================================================================
40
41/// Buffer size for format_name_and_hex(): longest command name plus "(0xXX)" and a margin.
42inline constexpr size_t NAME_AND_HEX_BUFFER_SIZE = 40;
43
44/// @brief Format a name/value pair as "name(0xXX)", e.g. "execute(0x00)".
45inline std::string format_name_and_hex(const char *name, uint8_t value) {
46 std::array<char, NAME_AND_HEX_BUFFER_SIZE> buffer{};
47 std::snprintf(buffer.data(), buffer.size(), "%s(0x%02X)", name, value);
48 return std::string(buffer.data());
49}
50
51// ============================================================================
52// Key-material display formatting
53// ============================================================================
54
55/// @brief Format a 16-byte key as an uppercase, unseparated hex string for display.
56///
57/// The one deliberate place system-key bytes are formatted for display, shared by both
58/// key-recovery features so neither forks its own copy: 2W "Accept Foreign Pairing"
59/// (build_key_extraction_report(), key_extraction_responder.cpp) and 1W controller-key adoption
60/// (build_oneway_adoption_report(), oneway_key_adoption.cpp). See redaction.h for the masking rules this
61/// intentionally does not apply to — both callers are the deliberate exception, not a loosening
62/// of it.
63/// @param key Pointer to AES_KEY_SIZE key bytes.
64/// @return Uppercase hex string, e.g. "0102030405060708090A0B0C0D0E0F10".
65inline std::string format_key_hex(const uint8_t key[AES_KEY_SIZE]) {
66 std::string out;
67 out.reserve(AES_KEY_SIZE * 2);
68 char byte_buf[3];
69 for (uint8_t i = 0; i < AES_KEY_SIZE; i++) {
70 snprintf(byte_buf, sizeof(byte_buf), "%02X", key[i]);
71 out += byte_buf;
72 }
73 return out;
74}
75
76/// A key check value: the first three bytes of the key's AES encryption of a zero block. It names a
77/// key without revealing it, so it is safe in any log line and in a report that is not bannered.
78/// @param key Pointer to AES_KEY_SIZE key bytes.
79/// @return Six uppercase hex digits, or "------" if the check value could not be computed.
80inline std::string format_key_check_value(const uint8_t key[AES_KEY_SIZE]) {
81 uint8_t kcv[crypto::KEY_CHECK_VALUE_SIZE];
82 if (!crypto::key_check_value(key, kcv))
83 return "------";
84 char buf[(2 * crypto::KEY_CHECK_VALUE_SIZE) + 1];
85 snprintf(buf, sizeof(buf), "%02X%02X%02X", kcv[0], kcv[1], kcv[2]);
86 return std::string(buf);
87}
88
89// ============================================================================
90// Logging helpers
91// ============================================================================
92
93/// @brief Log a frame at the "io_capture" tag with structured fields.
94/// Used for protocol‑level debugging (phases: component, tx, rx, parse_ok/parse_fail).
95/// @param radio Radio driver instance (provides chip name and capture).
96/// @param stage String label for the current phase.
97/// @param buf Raw bytes being logged.
98/// @param len Length of buf.
99/// @param frame Optional parsed IoFrame for decoded fields (cmd, src, dst).
100inline void log_component_capture(const RadioDriver *radio, const char *stage, const uint8_t *buf, uint8_t len,
101 const IoFrame *frame = nullptr) {
102 const RadioCaptureInfo &capture = radio->get_last_capture();
103 char payload_hex[FRAME_LOG_HEX_BUFFER_SIZE];
104 // Masks the 0x32 key-transfer payload exactly like log_frame() (log_frame.h) — this path is
105 // separate from log_frame() and runs on every received frame, including a passively overheard
106 // pairing exchange between two other devices, so it must carry the same redaction guarantee.
107 render_frame_hex_redacted(buf, len, payload_hex, sizeof(payload_hex));
108 if (frame != nullptr) {
109 ESP_LOGD("io_capture",
110 "chip=%s phase=component stage=%s freq=%" PRIu32 " ts=%" PRIu32
111 " len=%u cmd=0x%02X src=%02X%02X%02X dst=%02X%02X%02X payload=%s",
112 radio->chip_name(), stage, capture.freq_hz, capture.timestamp_ms, len, frame->cmd, frame->src[0],
113 frame->src[1], frame->src[2], frame->dst[0], frame->dst[1], frame->dst[2], payload_hex);
114 return;
115 }
116 ESP_LOGD("io_capture", "chip=%s phase=component stage=%s freq=%" PRIu32 " ts=%" PRIu32 " len=%u payload=%s",
117 radio->chip_name(), stage, capture.freq_hz, capture.timestamp_ms, len, payload_hex);
118}
119
120/// @brief Log `prefix` followed by `message`, one line per log call rather than one call for the
121/// whole (possibly multi-line) string.
122///
123/// ESPHome formats each log call into a fixed 512-byte buffer (`ESPHOME_LOGGER_TX_BUFFER_SIZE`,
124/// esphome/core/defines.h) and silently truncates anything longer; a multi-line report (a YAML
125/// snippet plus explanatory prose) routinely exceeds that and truncates mid-line if logged as a
126/// single call — confirmed on real hardware for both call sites this function serves:
127/// `scan_paired_devices()`'s report (a multi-device report cut off mid-snippet) and 1W
128/// controller-key adoption's report (the recovered `system_key` line itself never made it into
129/// the log at all). Splitting by line keeps every individual call's payload small regardless of
130/// how long the full message is. Shared rather than duplicated a third time — a second private
131/// copy is exactly how the 1W path ended up with the bug this fixes.
132/// @param tag Log tag.
133/// @param is_warning True to log at WARN, false for INFO.
134/// @param prefix Prepended to the message's first line only (e.g. "Management action X: ").
135/// @param message Message to log; may contain embedded `\n` line breaks.
136inline void log_multiline_result(const char *tag, bool is_warning, const std::string &prefix,
137 const std::string &message) {
138 size_t start = 0;
139 bool first = true;
140 while (true) {
141 const size_t end = message.find('\n', start);
142 const std::string line = (end == std::string::npos) ? message.substr(start) : message.substr(start, end - start);
143 const std::string out = first ? prefix + line : line;
144 if (is_warning) {
145 ESP_LOGW(tag, "%s", out.c_str());
146 } else {
147 ESP_LOGI(tag, "%s", out.c_str());
148 }
149 first = false;
150 if (end == std::string::npos || end + 1 >= message.size())
151 break;
152 start = end + 1;
153 }
154}
155
156} // namespace detail
157} // namespace home_io_control
158} // namespace esphome
Abstract radio driver for IO-Homecontrol.
const RadioCaptureInfo & get_last_capture() const
Get the most recent radio capture info.
virtual const char * chip_name() const =0
Get a human‑readable chip name.
Shared frame logging helpers for IO-Homecontrol.
bool key_check_value(const uint8_t key[AES_KEY_SIZE], uint8_t out[KEY_CHECK_VALUE_SIZE])
Compute a key check value. See proto_crypto.h for the full contract.
std::string format_name_and_hex(const char *name, uint8_t value)
Format a name/value pair as "name(0xXX)", e.g. "execute(0x00)".
Definition log_helpers.h:45
constexpr const char * TAG
Shared log tag for hub-level messages.
Definition log_helpers.h:32
constexpr size_t NAME_AND_HEX_BUFFER_SIZE
Buffer size for format_name_and_hex(): longest command name plus "(0xXX)" and a margin.
Definition log_helpers.h:42
void log_multiline_result(const char *tag, bool is_warning, const std::string &prefix, const std::string &message)
Log prefix followed by message, one line per log call rather than one call for the whole (possibly mu...
void log_component_capture(const RadioDriver *radio, const char *stage, const uint8_t *buf, uint8_t len, const IoFrame *frame=nullptr)
Log a frame at the "io_capture" tag with structured fields.
constexpr const char * FRAME_TAG
Log tag of the "received a frame we did not act on" lines (log_frame_issue()), separate from TAG so a...
Definition log_helpers.h:35
std::string format_key_hex(const uint8_t key[AES_KEY_SIZE])
Format a 16-byte key as an uppercase, unseparated hex string for display.
Definition log_helpers.h:65
std::string format_key_check_value(const uint8_t key[AES_KEY_SIZE])
A key check value: the first three bytes of the key's AES encryption of a zero block.
Definition log_helpers.h:80
void render_frame_hex_redacted(const uint8_t *data, uint8_t len, char *out, size_t out_size)
Render a frame's bytes as spaced hex text, masking the payload when the command carries key material ...
Definition log_frame.h:72
constexpr size_t FRAME_LOG_HEX_BUFFER_SIZE
Fits a full 32-byte frame rendered as spaced hex text.
Definition log_frame.h:20
Cryptographic helpers for the IO‑Homecontrol protocol.
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Fundamental IO-Homecontrol frame and crypto size constants.
Radio abstraction layer for IO-Homecontrol.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Definition proto_frame.h:93
Diagnostic capture from a radio operation.
uint32_t timestamp_ms
Timestamp of capture (millis).
uint32_t freq_hz
RF frequency of capture (Hz).