mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-18 19:13:58 +00:00
hotfix(analyzer): sync IR Fox RX terminal behavior
This commit is contained in:
@ -1,8 +1,20 @@
|
||||
#include "IrFoxDecoder.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#define CHECK(expression) \
|
||||
do \
|
||||
{ \
|
||||
if (!(expression)) \
|
||||
{ \
|
||||
std::fprintf(stderr, "CHECK failed: %s (%s:%d)\n", #expression, __FILE__, __LINE__); \
|
||||
std::exit(EXIT_FAILURE); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
namespace {
|
||||
|
||||
uint8_t crc8(const uint8_t* data, uint8_t end, uint8_t poly)
|
||||
@ -22,23 +34,33 @@ struct DecoderHarness
|
||||
IrFoxDecoder decoder;
|
||||
std::vector<IrFoxEmitPacket> packets;
|
||||
std::vector<IrFoxEmitBit> events;
|
||||
std::vector<IrFoxEmitTerminal> terminals;
|
||||
uint64_t phase = 0;
|
||||
bool collect_bit_events = true;
|
||||
static constexpr uint32_t kFs = 1000000U;
|
||||
|
||||
DecoderHarness()
|
||||
explicit DecoderHarness(bool collect_bits = true) : collect_bit_events(collect_bits)
|
||||
{
|
||||
decoder.reset();
|
||||
}
|
||||
|
||||
void edge(uint64_t sample, bool rising)
|
||||
{
|
||||
decoder.processEdge(sample, rising, kFs, [this](const IrFoxEmitBit& event) { events.push_back(event); },
|
||||
[this](const IrFoxEmitPacket& packet) { packets.push_back(packet); });
|
||||
IrFoxOnBit on_bit;
|
||||
if (collect_bit_events)
|
||||
on_bit = [this](const IrFoxEmitBit& event) { events.push_back(event); };
|
||||
decoder.processEdge(sample, rising, kFs, on_bit,
|
||||
[this](const IrFoxEmitPacket& packet) { packets.push_back(packet); },
|
||||
[this](const IrFoxEmitTerminal& terminal) { terminals.push_back(terminal); });
|
||||
}
|
||||
|
||||
void lockPreamble()
|
||||
{
|
||||
constexpr uint64_t first_rise = 40000;
|
||||
lockPreambleAt(40000U);
|
||||
}
|
||||
|
||||
void lockPreambleAt(uint64_t first_rise)
|
||||
{
|
||||
constexpr uint64_t period = irfox::kBitTimeUs * 3U;
|
||||
edge(first_rise, true);
|
||||
edge(first_rise + 700U, false);
|
||||
@ -69,25 +91,73 @@ struct DecoderHarness
|
||||
emitCell(sync);
|
||||
}
|
||||
}
|
||||
|
||||
void emitPacket(const std::vector<uint8_t>& packet)
|
||||
{
|
||||
for (size_t i = 0; i < packet.size(); ++i)
|
||||
emitByte(packet[i], i + 1U != packet.size());
|
||||
}
|
||||
|
||||
void flushAt(uint64_t sample)
|
||||
{
|
||||
IrFoxOnBit on_bit;
|
||||
if (collect_bit_events)
|
||||
on_bit = [this](const IrFoxEmitBit& event) { events.push_back(event); };
|
||||
decoder.flushEnd(sample, kFs, on_bit,
|
||||
[this](const IrFoxEmitPacket& packet) { packets.push_back(packet); },
|
||||
[this](const IrFoxEmitTerminal& terminal) { terminals.push_back(terminal); });
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint8_t> makeValidPacket()
|
||||
{
|
||||
std::vector<uint8_t> packet{0xE7, 0x00, 0x01, 0x00, 0x2A, 0x00, 0x00};
|
||||
packet[5] = crc8(packet.data(), 5, irfox::kPoly1);
|
||||
packet[6] = crc8(packet.data(), 6, irfox::kPoly2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
void assertBadLengthAbortsAndRecovers(uint8_t declared_size)
|
||||
{
|
||||
DecoderHarness harness;
|
||||
harness.lockPreamble();
|
||||
const uint8_t header = static_cast<uint8_t>(0xE0U | declared_size);
|
||||
harness.emitByte(header, false);
|
||||
|
||||
CHECK(harness.packets.empty());
|
||||
CHECK(harness.terminals.size() == 1U);
|
||||
CHECK(harness.terminals[0].reason == IrFoxTerminalReason::Abort);
|
||||
CHECK(harness.terminals[0].cause == IrFoxAbortCause::BadLength);
|
||||
CHECK(harness.terminals[0].message_type == 7U);
|
||||
CHECK(harness.terminals[0].declared_size == declared_size);
|
||||
CHECK(harness.terminals[0].received_bits == irfox::kBitPerByte);
|
||||
|
||||
// Firmware abortFrame() releases the 30.288 ms preamble guard. A receiver
|
||||
// that merely sets is_wrong_pack will miss this complete nearby frame.
|
||||
const uint64_t next_preamble = harness.phase + 5000U;
|
||||
harness.lockPreambleAt(next_preamble);
|
||||
harness.emitPacket(makeValidPacket());
|
||||
|
||||
CHECK(harness.packets.size() == 1U);
|
||||
CHECK(harness.packets[0].crc_ok);
|
||||
CHECK(harness.packets[0].start_sample == static_cast<int64_t>(next_preamble));
|
||||
CHECK(harness.terminals.size() == 1U);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
uint8_t packet[] = {0xE7, 0x00, 0x01, 0x00, 0x2A, 0x00, 0x00};
|
||||
packet[5] = crc8(packet, 5, irfox::kPoly1);
|
||||
packet[6] = crc8(packet, 6, irfox::kPoly2);
|
||||
const std::vector<uint8_t> packet = makeValidPacket();
|
||||
|
||||
DecoderHarness valid;
|
||||
valid.lockPreamble();
|
||||
for (uint8_t i = 0; i < sizeof packet; ++i)
|
||||
valid.emitByte(packet[i], i + 1U != sizeof packet);
|
||||
assert(valid.packets.size() == 1U);
|
||||
assert(valid.packets[0].crc_ok);
|
||||
assert(valid.packets[0].pack_size == sizeof packet);
|
||||
assert(valid.packets[0].start_sample == 40000);
|
||||
assert(valid.packets[0].start_sample < valid.packets[0].end_sample);
|
||||
valid.emitPacket(packet);
|
||||
CHECK(valid.packets.size() == 1U);
|
||||
CHECK(valid.packets[0].crc_ok);
|
||||
CHECK(valid.packets[0].pack_size == packet.size());
|
||||
CHECK(valid.packets[0].start_sample == 40000);
|
||||
CHECK(valid.packets[0].start_sample < valid.packets[0].end_sample);
|
||||
bool saw_preamble = false;
|
||||
std::vector<uint8_t> decoded_bytes;
|
||||
for (const IrFoxEmitBit& event : valid.events)
|
||||
@ -95,21 +165,118 @@ int main()
|
||||
if (event.frame_type == IRF_FT_PREAMBLE)
|
||||
{
|
||||
saw_preamble = true;
|
||||
assert(event.start_sample == 40000);
|
||||
CHECK(event.start_sample == 40000);
|
||||
}
|
||||
if (event.frame_type == IRF_FT_DATA_BYTE)
|
||||
decoded_bytes.push_back(static_cast<uint8_t>(event.bit_value));
|
||||
}
|
||||
assert(saw_preamble);
|
||||
assert(decoded_bytes.size() == sizeof packet);
|
||||
for (uint8_t i = 0; i < sizeof packet; ++i)
|
||||
assert(decoded_bytes[i] == packet[i]);
|
||||
CHECK(saw_preamble);
|
||||
CHECK(decoded_bytes.size() == packet.size());
|
||||
for (size_t i = 0; i < packet.size(); ++i)
|
||||
CHECK(decoded_bytes[i] == packet[i]);
|
||||
|
||||
DecoderHarness too_short;
|
||||
too_short.lockPreamble();
|
||||
too_short.emitByte(0xE1, false);
|
||||
assert(too_short.packets.size() == 1U);
|
||||
assert(!too_short.packets[0].crc_ok);
|
||||
assert(too_short.packets[0].pack_size == 1U);
|
||||
for (uint8_t declared_size = 0; declared_size < irfox::kMsgBytes + irfox::kCrcBytes; ++declared_size)
|
||||
assertBadLengthAbortsAndRecovers(declared_size);
|
||||
|
||||
// Overview supplies no per-bit callback. Terminal reporting must not depend
|
||||
// on Detailed-mode bit/event generation.
|
||||
DecoderHarness bad_sync(false);
|
||||
bad_sync.lockPreamble();
|
||||
bad_sync.emitByte(0xE7, false);
|
||||
// Header 0xE7 ends in one, while the first sync bit must be inverted.
|
||||
bad_sync.emitCell(true);
|
||||
CHECK(bad_sync.terminals.empty());
|
||||
bad_sync.emitCell(true);
|
||||
CHECK(bad_sync.terminals.empty());
|
||||
bad_sync.emitCell(true);
|
||||
CHECK(bad_sync.events.empty());
|
||||
CHECK(bad_sync.terminals.size() == 1U);
|
||||
CHECK(bad_sync.terminals[0].reason == IrFoxTerminalReason::Abort);
|
||||
CHECK(bad_sync.terminals[0].cause == IrFoxAbortCause::BadSync);
|
||||
CHECK(bad_sync.terminals[0].message_type == 7U);
|
||||
CHECK(bad_sync.terminals[0].declared_size == 7U);
|
||||
CHECK(bad_sync.terminals[0].received_bits == irfox::kBitPerByte);
|
||||
const uint64_t after_sync_abort = bad_sync.phase + 5000U;
|
||||
bad_sync.lockPreambleAt(after_sync_abort);
|
||||
bad_sync.emitPacket(packet);
|
||||
CHECK(bad_sync.packets.size() == 1U);
|
||||
CHECK(bad_sync.packets[0].crc_ok);
|
||||
CHECK(bad_sync.packets[0].start_sample == static_cast<int64_t>(after_sync_abort));
|
||||
CHECK(bad_sync.terminals.size() == 1U);
|
||||
|
||||
DecoderHarness stale_candidate;
|
||||
constexpr uint64_t stale_rise = 40000U;
|
||||
const uint64_t candidate_gap =
|
||||
irfox::irTimeoutUs(irfox::kBitTimeUs) + 1U; // New 1x timeout, still below the old 3x timeout.
|
||||
const uint64_t fresh_preamble = stale_rise + candidate_gap;
|
||||
stale_candidate.edge(stale_rise, true);
|
||||
stale_candidate.lockPreambleAt(fresh_preamble);
|
||||
stale_candidate.emitPacket(packet);
|
||||
CHECK(stale_candidate.packets.size() == 1U);
|
||||
CHECK(stale_candidate.packets[0].crc_ok);
|
||||
CHECK(stale_candidate.packets[0].start_sample == static_cast<int64_t>(fresh_preamble));
|
||||
CHECK(stale_candidate.terminals.empty());
|
||||
|
||||
// If reception times out after PRE lock but before the first data bit, the
|
||||
// Detailed terminal span must begin immediately after the separate PRE frame.
|
||||
DecoderHarness pre_only;
|
||||
pre_only.lockPreamble();
|
||||
const IrFoxEmitBit* pre_event = nullptr;
|
||||
for (const IrFoxEmitBit& event : pre_only.events)
|
||||
{
|
||||
if (event.frame_type == IRF_FT_PREAMBLE)
|
||||
pre_event = &event;
|
||||
}
|
||||
CHECK(pre_event != nullptr);
|
||||
constexpr uint64_t preamble_period = irfox::kBitTimeUs * 3U;
|
||||
const uint64_t pre_lock_edge = 40000U + preamble_period * 2U;
|
||||
const uint64_t abort_silence = 2U * irfox::irTimeoutUs(irfox::kBitTimeUs);
|
||||
pre_only.flushAt(pre_lock_edge + abort_silence + 1U);
|
||||
CHECK(pre_only.terminals.size() == 1U);
|
||||
CHECK(pre_only.terminals[0].reason == IrFoxTerminalReason::Timeout);
|
||||
CHECK(pre_only.terminals[0].received_bits == 0U);
|
||||
CHECK(pre_only.terminals[0].detail_start_sample == pre_event->end_sample + 1);
|
||||
CHECK(pre_only.terminals[0].end_sample == static_cast<int64_t>(pre_lock_edge + abort_silence));
|
||||
|
||||
DecoderHarness truncated;
|
||||
truncated.lockPreamble();
|
||||
truncated.emitByte(0xE7, true);
|
||||
CHECK(truncated.packets.empty());
|
||||
truncated.flushAt(truncated.phase + abort_silence);
|
||||
CHECK(truncated.terminals.empty());
|
||||
truncated.flushAt(truncated.phase + abort_silence + 1U);
|
||||
CHECK(truncated.packets.empty());
|
||||
CHECK(truncated.terminals.size() == 1U);
|
||||
CHECK(truncated.terminals[0].reason == IrFoxTerminalReason::Timeout);
|
||||
CHECK(truncated.terminals[0].cause == IrFoxAbortCause::None);
|
||||
CHECK(truncated.terminals[0].message_type == 7U);
|
||||
CHECK(truncated.terminals[0].declared_size == 7U);
|
||||
CHECK(truncated.terminals[0].received_bits == irfox::kBitPerByte);
|
||||
CHECK(truncated.terminals[0].end_sample == static_cast<int64_t>(truncated.phase + abort_silence));
|
||||
truncated.flushAt(truncated.phase + abort_silence + 100U);
|
||||
CHECK(truncated.terminals.size() == 1U);
|
||||
const uint64_t after_timeout = truncated.phase + abort_silence + 5000U;
|
||||
truncated.lockPreambleAt(after_timeout);
|
||||
truncated.emitPacket(packet);
|
||||
CHECK(truncated.packets.size() == 1U);
|
||||
CHECK(truncated.packets[0].crc_ok);
|
||||
CHECK(truncated.packets[0].start_sample == static_cast<int64_t>(after_timeout));
|
||||
CHECK(truncated.terminals.size() == 1U);
|
||||
|
||||
// With no flush/tick between frames, the first rise strictly beyond 2T both
|
||||
// closes the old frame and opens the new preamble. Inclusive spans must not
|
||||
// share that sample.
|
||||
DecoderHarness adjacent_timeout;
|
||||
adjacent_timeout.lockPreamble();
|
||||
adjacent_timeout.emitByte(0xE7, true);
|
||||
const uint64_t adjacent_preamble = adjacent_timeout.phase + abort_silence + 1U;
|
||||
adjacent_timeout.lockPreambleAt(adjacent_preamble);
|
||||
adjacent_timeout.emitPacket(packet);
|
||||
CHECK(adjacent_timeout.terminals.size() == 1U);
|
||||
CHECK(adjacent_timeout.terminals[0].reason == IrFoxTerminalReason::Timeout);
|
||||
CHECK(adjacent_timeout.packets.size() == 1U);
|
||||
CHECK(adjacent_timeout.packets[0].crc_ok);
|
||||
CHECK(adjacent_timeout.terminals[0].end_sample < adjacent_timeout.packets[0].start_sample);
|
||||
CHECK(adjacent_timeout.packets[0].start_sample == static_cast<int64_t>(adjacent_preamble));
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user