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_frame.h"
12#include "proto_sizes.h"
13#include "radio_interface.h"
14
15#include "esphome/core/log.h"
16
17#include <array>
18#include <cinttypes>
19#include <cstdint>
20#include <cstdio>
21#include <string>
22
23namespace esphome {
24namespace home_io_control {
25namespace detail {
26
27// ============================================================================
28// Shared constants
29// ============================================================================
30
31inline constexpr const char *TAG = "home_io_control"; ///< Shared log tag for hub-level messages.
32
33// ============================================================================
34// Formatting helpers
35// ============================================================================
36
37/// Buffer size for format_name_and_hex(): longest command name plus "(0xXX)" and a margin.
38inline constexpr size_t NAME_AND_HEX_BUFFER_SIZE = 40;
39
40/// @brief Format a name/value pair as "name(0xXX)", e.g. "execute(0x00)".
41inline std::string format_name_and_hex(const char *name, uint8_t value) {
42 std::array<char, NAME_AND_HEX_BUFFER_SIZE> buffer{};
43 std::snprintf(buffer.data(), buffer.size(), "%s(0x%02X)", name, value);
44 return std::string(buffer.data());
45}
46
47// ============================================================================
48// Key-material display formatting
49// ============================================================================
50
51/// @brief Format a 16-byte key as an uppercase, unseparated hex string for display.
52///
53/// The one deliberate place system-key bytes are formatted for display, shared by both
54/// key-recovery features so neither forks its own copy: 2W "Accept Foreign Pairing"
55/// (build_key_extraction_report(), key_extraction_responder.cpp) and 1W controller-key adoption
56/// (build_oneway_adoption_report(), oneway_key_adoption.cpp). See redaction.h for the masking rules this
57/// intentionally does not apply to — both callers are the deliberate exception, not a loosening
58/// of it.
59/// @param key Pointer to AES_KEY_SIZE key bytes.
60/// @return Uppercase hex string, e.g. "0102030405060708090A0B0C0D0E0F10".
61inline std::string format_key_hex(const uint8_t key[AES_KEY_SIZE]) {
62 std::string out;
63 out.reserve(AES_KEY_SIZE * 2);
64 char byte_buf[3];
65 for (uint8_t i = 0; i < AES_KEY_SIZE; i++) {
66 snprintf(byte_buf, sizeof(byte_buf), "%02X", key[i]);
67 out += byte_buf;
68 }
69 return out;
70}
71
72// ============================================================================
73// Logging helpers
74// ============================================================================
75
76/// @brief Log a frame at the "io_capture" tag with structured fields.
77/// Used for protocol‑level debugging (phases: component, tx, rx, parse_ok/parse_fail).
78/// @param radio Radio driver instance (provides chip name and capture).
79/// @param stage String label for the current phase.
80/// @param buf Raw bytes being logged.
81/// @param len Length of buf.
82/// @param frame Optional parsed IoFrame for decoded fields (cmd, src, dst).
83inline void log_component_capture(const RadioDriver *radio, const char *stage, const uint8_t *buf, uint8_t len,
84 const IoFrame *frame = nullptr) {
85 const RadioCaptureInfo &capture = radio->get_last_capture();
86 char payload_hex[FRAME_LOG_HEX_BUFFER_SIZE];
87 // Masks the 0x32 key-transfer payload exactly like log_frame() (log_frame.h) — this path is
88 // separate from log_frame() and runs on every received frame, including a passively overheard
89 // pairing exchange between two other devices, so it must carry the same redaction guarantee.
90 render_frame_hex_redacted(buf, len, payload_hex, sizeof(payload_hex));
91 if (frame != nullptr) {
92 ESP_LOGD("io_capture",
93 "chip=%s phase=component stage=%s freq=%" PRIu32 " ts=%" PRIu32
94 " len=%u cmd=0x%02X src=%02X%02X%02X dst=%02X%02X%02X payload=%s",
95 radio->chip_name(), stage, capture.freq_hz, capture.timestamp_ms, len, frame->cmd, frame->src[0],
96 frame->src[1], frame->src[2], frame->dst[0], frame->dst[1], frame->dst[2], payload_hex);
97 return;
98 }
99 ESP_LOGD("io_capture", "chip=%s phase=component stage=%s freq=%" PRIu32 " ts=%" PRIu32 " len=%u payload=%s",
100 radio->chip_name(), stage, capture.freq_hz, capture.timestamp_ms, len, payload_hex);
101}
102
103/// @brief Log `prefix` followed by `message`, one line per log call rather than one call for the
104/// whole (possibly multi-line) string.
105///
106/// ESPHome formats each log call into a fixed 512-byte buffer (`ESPHOME_LOGGER_TX_BUFFER_SIZE`,
107/// esphome/core/defines.h) and silently truncates anything longer; a multi-line report (a YAML
108/// snippet plus explanatory prose) routinely exceeds that and truncates mid-line if logged as a
109/// single call — confirmed on real hardware for both call sites this function serves:
110/// `scan_paired_devices()`'s report (a multi-device report cut off mid-snippet) and 1W
111/// controller-key adoption's report (the recovered `system_key` line itself never made it into
112/// the log at all). Splitting by line keeps every individual call's payload small regardless of
113/// how long the full message is. Shared rather than duplicated a third time — a second private
114/// copy is exactly how the 1W path ended up with the bug this fixes.
115/// @param tag Log tag.
116/// @param is_warning True to log at WARN, false for INFO.
117/// @param prefix Prepended to the message's first line only (e.g. "Management action X: ").
118/// @param message Message to log; may contain embedded `\n` line breaks.
119inline void log_multiline_result(const char *tag, bool is_warning, const std::string &prefix,
120 const std::string &message) {
121 size_t start = 0;
122 bool first = true;
123 while (true) {
124 const size_t end = message.find('\n', start);
125 const std::string line = (end == std::string::npos) ? message.substr(start) : message.substr(start, end - start);
126 const std::string out = first ? prefix + line : line;
127 if (is_warning) {
128 ESP_LOGW(tag, "%s", out.c_str());
129 } else {
130 ESP_LOGI(tag, "%s", out.c_str());
131 }
132 first = false;
133 if (end == std::string::npos || end + 1 >= message.size())
134 break;
135 start = end + 1;
136 }
137}
138
139} // namespace detail
140} // namespace home_io_control
141} // 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.
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:41
constexpr const char * TAG
Shared log tag for hub-level messages.
Definition log_helpers.h:31
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:38
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.
Definition log_helpers.h:83
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:61
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:71
constexpr size_t FRAME_LOG_HEX_BUFFER_SIZE
Fits a full 32-byte frame rendered as spaced hex text.
Definition log_frame.h:19
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).