30uint8_t get_bit_msb(
const uint8_t *data, uint16_t bit_pos) {
return (data[bit_pos / 8] >> (7 - (bit_pos % 8))) & 0x01; }
45uint8_t
decode_uart_probe(
const uint8_t *raw, uint8_t raw_len, uint8_t bit_offset, uint8_t *decoded,
46 uint8_t decoded_max_len) {
51 uint16_t bit_pos = bit_offset;
52 uint16_t
const total_bits = raw_len * 8;
53 uint8_t decoded_len = 0;
55 while (bit_pos + 10 <= total_bits && decoded_len < decoded_max_len) {
56 if (get_bit_msb(raw, bit_pos) != 0 || get_bit_msb(raw, bit_pos + 9) != 1)
60 for (uint8_t index = 0; index < 8; index++)
61 value |= get_bit_msb(raw, bit_pos + 1 + index) << index;
63 decoded[decoded_len++] = value;
85 case CMD_PRIVATE_RESP:
87 case CMD_PRIVATE2_RESP:
89 case CMD_WRITE_PRIVATE:
90 case CMD_WRITE_PRIVATE_ACK:
91 case CMD_LIMITATION_STATUS_REQ:
92 case CMD_LIMITATION_STATUS_RESP:
93 case CMD_DISCOVER_REQ:
94 case CMD_DISCOVER_RESP:
95 case CMD_DISCOVER_SPE_REQ:
96 case CMD_DISCOVER_SPE_RESP:
97 case CMD_DISCOVER_CONFIRM:
98 case CMD_DISCOVER_CONFIRM_ACK:
102 case CMD_DISCOVER_ALT_REQ:
103 case CMD_DISCOVER_ALT_RESP:
105 case CMD_KEY_TRANSFER:
106 case CMD_KEY_CONFIRM:
107 case CMD_LAUNCH_KEY_TRANSFER:
108 case CMD_NODE_VERIFY_REQ:
109 case CMD_NODE_VERIFY_RESP:
110 case CMD_CHALLENGE_REQ:
111 case CMD_CHALLENGE_RESP:
112 case CMD_UNKNOWN4A_RESP:
114 case CMD_GET_NAME_RESP:
116 case CMD_SET_NAME_RESP:
118 case CMD_GET_INFO1_RESP:
120 case CMD_GET_INFO2_RESP:
121 case CMD_GET_GENERAL_INFO3:
122 case CMD_GET_GENERAL_INFO3_RESP:
123 case CMD_SET_CONFIG1:
124 case CMD_SET_CONFIG1_RESP:
125 case CMD_STATUS_UPDATE:
126 case CMD_STATUS_UPDATE_RESP:
143bool is_plausible_uart_frame(
const IoFrame &frame, uint8_t candidate_len) {
144 if (candidate_len < FRAME_MIN_SIZE)
148 return (frame.ctrl0 & CTRL0_PROTOCOL_1W) != 0;
158 for (uint8_t start = 0; start < decoded_len; start++) {
168 const uint8_t max_candidate_len = std::min<uint8_t>(decoded_len - start,
FRAME_MAX_WIRE_SIZE);
169 for (uint8_t candidate_len = max_candidate_len; candidate_len >= FRAME_MIN_SIZE; candidate_len--) {
171 if (!
parse(decoded + start, candidate_len, frame))
173 if (!is_plausible_uart_frame(frame, candidate_len))
175 if (start + candidate_len + 2 > decoded_len)
177 const uint16_t computed_crc =
crc_ccitt(decoded + start, candidate_len);
178 const uint16_t received_crc =
179 (uint16_t) decoded[start + candidate_len] | ((uint16_t) decoded[start + candidate_len + 1] << 8);
180 if (computed_crc != received_crc)
182 return {start, candidate_len};
198 for (uint8_t bit_offset = 0; bit_offset < UART_PROBE_MAX_BIT_OFFSET; bit_offset++) {
200 uint8_t
const decoded_len =
decode_uart_probe(raw, raw_len, bit_offset, decoded,
sizeof(decoded));
201 if (decoded_len == 0)
207 memcpy(best.
decoded, decoded, decoded_len);
217 memcpy(best.
decoded, decoded, decoded_len);
231 const uint16_t bits = cells * UART_CELL_BITS;
232 return (uint8_t) ((bits + 7) / 8);
242 for (uint8_t bit_offset = 0; bit_offset < UART_PROBE_MAX_BIT_OFFSET; bit_offset++) {
246 const auto frame_len = (uint8_t) ((ctrl0 & CTRL0_LENGTH_MASK) + 1);
247 if (frame_len < FRAME_MIN_SIZE || frame_len > FRAME_MAX_SIZE)
249 best = std::max(frame_len, best);
256uint8_t
uart_encode_packet(
const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len) {
257 if (len == 0 || encoded_max_len == 0)
260 memset(encoded, 0, encoded_max_len);
261 uint16_t bit_pos = 0;
262 const uint16_t total_bits = len * 10;
263 if (((total_bits + 7) / 8) > encoded_max_len)
266 auto write_bit = [encoded](uint16_t pos, uint8_t bit) {
268 encoded[pos / 8] |= 1U << (7 - (pos % 8));
271 for (uint8_t byte_index = 0; byte_index < len; byte_index++) {
272 const uint8_t value = data[byte_index];
274 write_bit(bit_pos++, 0);
275 for (uint8_t bit_index = 0; bit_index < 8; bit_index++)
276 write_bit(bit_pos++, (value >> bit_index) & 0x01);
277 write_bit(bit_pos++, 1);
285 const uint8_t encoded_len = (total_bits + 7) / 8;
286 for (uint16_t pad_pos = total_bits; pad_pos < (uint16_t) encoded_len * 8; pad_pos++)
287 write_bit(pad_pos, 1);
uint8_t decode_uart_probe(const uint8_t *raw, uint8_t raw_len, uint8_t bit_offset, uint8_t *decoded, uint8_t decoded_max_len)
Decode a raw UART‑encoded bitstream into bytes.
constexpr uint32_t soft_phy_air_time_us(uint32_t raw_bytes)
On-air time in microseconds for raw_bytes bytes at the protocol's line rate.
bool is_known_io_command(uint8_t cmd)
This list missing CMD_GET_GENERAL_INFO3_RESP (0x59) is exactly what turned a real Q2 probe reply into...
uint8_t soft_phy_peek_frame_length(const uint8_t *raw, uint8_t raw_len)
Recover a frame's total length from the very first UART cell of a reception.
uint8_t uart_encode_packet(const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len)
UART-encode a buffer of bytes (start bit 0, 8 data bits LSB-first, stop bit 1).
UartProbeResult find_uart_probe(const uint8_t *raw, uint8_t raw_len)
Search raw RX buffer for the best CRC-validated IO-Homecontrol frame.
uint16_t crc_ccitt(const uint8_t *data, uint8_t len)
CRC-CCITT used by the IO-Homecontrol protocol for frame validation.
static std::pair< uint8_t, uint8_t > find_crc_valid_frame(const uint8_t *decoded, uint8_t decoded_len)
Try to find a CRC-valid IO-Homecontrol frame within a decoded UART byte stream.
static constexpr uint8_t FRAME_MAX_WIRE_SIZE
Largest number of bytes a buffer must hold to receive or transmit any frame this project knows about,...
uint8_t soft_phy_raw_bytes_for_frame(uint8_t frame_len)
Raw on-air bytes needed to carry a whole frame: frame_len protocol bytes plus the two trailing CRC by...
bool parse(const uint8_t *buf, uint8_t buf_len, IoFrame &f)
Parse a wire buffer into a parsed IoFrame (validates length and CTRL0).
uint32_t io868_tx_air_time_us(uint16_t preamble_len, uint8_t frame_len)
Time on air of one 868 MHz transmission: preamble, sync word, and the frame plus its CRC,...
static constexpr uint8_t FRAME_CRC_SIZE
Size of the on-air CRC-CCITT trailer appended after every frame (declared bytes, plus the out-of-leng...
constexpr uint8_t RADIO_PACKET_BUFFER_SIZE
Scratch buffer size for raw radio packets and recovered frames.
IO-Homecontrol command IDs, result codes and protocol enumerations.
Software PHY for radios without IoHomeOn hardware framing.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Result of the UART probe: best candidate frame within a raw capture.
uint8_t decoded_len
Total number of bytes decoded at that offset.
uint8_t frame_start
Index into decoded buffer where the frame begins.
bool valid
A plausible frame was found.
uint8_t bit_offset
Bit offset where the best decode started.
uint8_t frame_len
Length of the candidate IoFrame (decoded bytes).
uint8_t decoded[RADIO_PACKET_BUFFER_SIZE]
Full decoded UART stream at the chosen offset.