mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-21 20:39:35 +00:00
Make IR timing and RX terminal state explicit
This commit is contained in:
60
tests/arduino_stubs/Arduino.h
Normal file
60
tests/arduino_stubs/Arduino.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class __FlashStringHelper;
|
||||
#define F(value) reinterpret_cast<const __FlashStringHelper *>(value)
|
||||
|
||||
struct GPIO_TypeDef
|
||||
{
|
||||
uint32_t BSRR = 0U;
|
||||
uint32_t IDR = 0U;
|
||||
};
|
||||
|
||||
using IRQn_Type = int;
|
||||
enum TimerFormat_t : uint8_t { TICK_FORMAT = 0, MICROSEC_FORMAT, HERTZ_FORMAT };
|
||||
|
||||
constexpr uint8_t LOW = 0U;
|
||||
constexpr uint8_t HIGH = 1U;
|
||||
constexpr uint8_t INPUT = 0U;
|
||||
constexpr uint8_t OUTPUT = 1U;
|
||||
|
||||
class HardwareTimer
|
||||
{
|
||||
public:
|
||||
void pause() {}
|
||||
void resume() {}
|
||||
void setOverflow(uint32_t value, TimerFormat_t = TICK_FORMAT) { overflow_ = value; }
|
||||
uint32_t getOverflow(TimerFormat_t = TICK_FORMAT) { return overflow_; }
|
||||
uint32_t getPrescaleFactor() { return 1U; }
|
||||
uint32_t getTimerClkFreq() { return 12000000U; }
|
||||
void attachInterrupt(uint8_t, void (*)()) {}
|
||||
|
||||
private:
|
||||
uint32_t overflow_ = 1U;
|
||||
};
|
||||
|
||||
inline GPIO_TypeDef arduinoStubPort;
|
||||
inline GPIO_TypeDef *digitalPinToPort(uint8_t) { return &arduinoStubPort; }
|
||||
inline uint16_t digitalPinToBitMask(uint8_t) { return 1U; }
|
||||
inline void pinMode(uint8_t, uint8_t) {}
|
||||
inline void digitalWrite(uint8_t, uint8_t) {}
|
||||
inline void NVIC_SetPriority(IRQn_Type, uint8_t) {}
|
||||
inline void noInterrupts() {}
|
||||
inline void interrupts() {}
|
||||
|
||||
inline uint32_t arduinoStubMicros = 0U;
|
||||
inline uint32_t micros() { return arduinoStubMicros; }
|
||||
inline uint32_t millis() { return arduinoStubMicros / 1000U; }
|
||||
|
||||
class Print
|
||||
{
|
||||
public:
|
||||
template <typename T> void print(const T &) {}
|
||||
template <typename T> void println(const T &) {}
|
||||
void println() {}
|
||||
};
|
||||
|
||||
using ArduinoSerialStub = Print;
|
||||
inline ArduinoSerialStub Serial;
|
||||
386
tests/test_rx_terminal.cpp
Normal file
386
tests/test_rx_terminal.cpp
Normal file
@ -0,0 +1,386 @@
|
||||
#include "IR_config.h"
|
||||
#include "RingBuffer.h"
|
||||
|
||||
// Test only: inspect the decoder state machine without adding production hooks.
|
||||
#define private public
|
||||
#include "IR_DecoderRaw.h"
|
||||
#undef private
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
uint32_t decoderTimeoutUs(const IR_DecoderRaw &decoder)
|
||||
{
|
||||
return static_cast<uint32_t>(decoder.riseSyncTime + tolerance) *
|
||||
static_cast<uint32_t>(bitPerByte + syncBits + 1U);
|
||||
}
|
||||
|
||||
uint32_t candidateTimeoutUs(const IR_DecoderRaw &decoder)
|
||||
{
|
||||
return decoderTimeoutUs(decoder) *
|
||||
static_cast<uint32_t>(IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT);
|
||||
}
|
||||
|
||||
uint8_t crc8Local(const uint8_t *data, uint8_t start, uint8_t end, uint8_t poly)
|
||||
{
|
||||
uint8_t crc = 0xFFU;
|
||||
for (uint8_t i = start; i < end; ++i)
|
||||
{
|
||||
crc ^= data[i];
|
||||
for (uint8_t bit = 0; bit < 8U; ++bit)
|
||||
crc = (crc & 0x80U) != 0U
|
||||
? static_cast<uint8_t>((crc << 1U) ^ poly)
|
||||
: static_cast<uint8_t>(crc << 1U);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void primeObservableCandidate(IR_DecoderRaw &decoder, uint32_t lastEdgeUs)
|
||||
{
|
||||
decoder.preambleState = IR_DecoderRaw::PreambleState::Candidate;
|
||||
decoder.preambleGoodPeriods = 1U;
|
||||
decoder.preambleWasObservable = true;
|
||||
decoder.preambleMeanPeriod = bitTime;
|
||||
decoder.preambleCandidateLastEdgeTime = lastEdgeUs;
|
||||
decoder.preambleCandidateFirstRiseTime = lastEdgeUs;
|
||||
decoder.preambleCandidateFirstRiseValid = true;
|
||||
decoder.isPreamb = true;
|
||||
decoder.isRecive = false;
|
||||
decoder.isReciveRaw = false;
|
||||
}
|
||||
|
||||
void primeLocked(IR_DecoderRaw &decoder, uint8_t msgType, uint8_t wireBytes)
|
||||
{
|
||||
decoder.preambleState = IR_DecoderRaw::PreambleState::Locked;
|
||||
decoder.isPreamb = false;
|
||||
decoder.isRecive = true;
|
||||
decoder.isReciveRaw = true;
|
||||
decoder.isWrongPack = false;
|
||||
decoder.isBufferOverflow = false;
|
||||
decoder.isAvailable = false;
|
||||
decoder.packSize = wireBytes;
|
||||
decoder.dataBuffer[0] =
|
||||
static_cast<uint8_t>((msgType << 5U) | (wireBytes & IR_MASK_MSG_INFO));
|
||||
decoder.i_dataBuffer = 8U;
|
||||
}
|
||||
|
||||
void verifyInitialSnapshot()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(terminal.seq == 0U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::None);
|
||||
assert(terminal.msgType == 0xFFU);
|
||||
assert(!terminal.hadLock);
|
||||
}
|
||||
|
||||
void verifyCandidateExpiresOnIdleTick()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t lastEdgeUs = 1000U;
|
||||
primeObservableCandidate(decoder, lastEdgeUs);
|
||||
assert(decoder.rxLineActive());
|
||||
|
||||
arduinoStubMicros = lastEdgeUs + candidateTimeoutUs(decoder) + 1U;
|
||||
decoder.tick();
|
||||
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(!decoder.rxLineActive());
|
||||
assert(decoder.preambleState == IR_DecoderRaw::PreambleState::Idle);
|
||||
assert(terminal.seq == 1U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(terminal.msgType == 0xFFU);
|
||||
assert(!terminal.hadLock);
|
||||
|
||||
++arduinoStubMicros;
|
||||
decoder.tick();
|
||||
assert(decoder.rxTerminalSeq() == terminal.seq);
|
||||
}
|
||||
|
||||
void emitEdge(IR_DecoderRaw &decoder, uint32_t timeUs, bool high)
|
||||
{
|
||||
arduinoStubMicros = timeUs;
|
||||
arduinoStubPort.IDR = high ? 1U : 0U;
|
||||
decoder.isr();
|
||||
decoder.tick();
|
||||
}
|
||||
|
||||
void queueEdge(IR_DecoderRaw &decoder, uint32_t timeUs, bool high)
|
||||
{
|
||||
arduinoStubMicros = timeUs;
|
||||
arduinoStubPort.IDR = high ? 1U : 0U;
|
||||
decoder.isr();
|
||||
}
|
||||
|
||||
void verifyCandidateIdleExpiryThroughPublicPipeline()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t firstRiseUs = decoderTimeoutUs(decoder) * 2U + 1000U;
|
||||
const uint32_t risePeriodUs = static_cast<uint32_t>(bitTime) * 5U / 2U;
|
||||
|
||||
emitEdge(decoder, firstRiseUs, true);
|
||||
emitEdge(decoder, firstRiseUs + risePeriodUs / 2U, false);
|
||||
emitEdge(decoder, firstRiseUs + risePeriodUs, true);
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
|
||||
arduinoStubMicros = firstRiseUs + risePeriodUs + candidateTimeoutUs(decoder) + 1U;
|
||||
decoder.tick();
|
||||
assert(!decoder.rxLineActive());
|
||||
assert(decoder.rxLastTerminal().reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(decoder.rxTerminalSeq() == 1U);
|
||||
}
|
||||
|
||||
void verifyCoarseResetPublishesThroughBatchedPublicPipeline()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t firstRiseUs = decoderTimeoutUs(decoder) * 2U + 1000U;
|
||||
const uint32_t goodPeriodUs = static_cast<uint32_t>(bitTime) * 5U / 2U;
|
||||
const uint32_t badPeriodUs = static_cast<uint32_t>(bitTime) * 4U;
|
||||
|
||||
queueEdge(decoder, firstRiseUs, true);
|
||||
queueEdge(decoder, firstRiseUs + goodPeriodUs / 2U, false);
|
||||
queueEdge(decoder, firstRiseUs + goodPeriodUs, true);
|
||||
queueEdge(decoder, firstRiseUs + goodPeriodUs + badPeriodUs / 2U, false);
|
||||
queueEdge(decoder, firstRiseUs + goodPeriodUs + badPeriodUs, true);
|
||||
decoder.tick();
|
||||
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
assert(decoder.rxLineActive());
|
||||
|
||||
// Continuing coarse-invalid edges below the timeout keep the potential
|
||||
// frame busy. They manufacture no terminal; a Car gate reaches its bounded
|
||||
// hard deadline and skips the optional tail instead of transmitting here.
|
||||
const uint32_t nextRiseUs =
|
||||
firstRiseUs + goodPeriodUs + badPeriodUs + badPeriodUs;
|
||||
queueEdge(decoder, nextRiseUs - badPeriodUs / 2U, false);
|
||||
queueEdge(decoder, nextRiseUs, true);
|
||||
decoder.tick();
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
assert(decoder.rxLineActive());
|
||||
|
||||
arduinoStubMicros = nextRiseUs + candidateTimeoutUs(decoder) + 1U;
|
||||
decoder.tick();
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(terminal.seq == 1U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(!terminal.hadLock);
|
||||
assert(!decoder.rxLineActive());
|
||||
}
|
||||
|
||||
void verifyFreshCandidateWithOnlyCoarseInvalidEdgesStaysActive()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t firstRiseUs = decoderTimeoutUs(decoder) * 2U + 1000U;
|
||||
const uint32_t badPeriodUs = static_cast<uint32_t>(bitTime) * 4U;
|
||||
const uint32_t startDeadlineUs = firstRiseUs + 58000U;
|
||||
const uint32_t hardDeadlineUs = firstRiseUs + 78000U;
|
||||
|
||||
emitEdge(decoder, firstRiseUs, true);
|
||||
assert(decoder.preambleState == IR_DecoderRaw::PreambleState::Candidate);
|
||||
assert(decoder.preambleGoodPeriods == 0U);
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
|
||||
uint32_t riseUs = firstRiseUs;
|
||||
while (riseUs + badPeriodUs <= startDeadlineUs)
|
||||
{
|
||||
emitEdge(decoder, riseUs + badPeriodUs / 2U, false);
|
||||
riseUs += badPeriodUs;
|
||||
emitEdge(decoder, riseUs, true);
|
||||
assert(decoder.preambleGoodPeriods == 0U);
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
}
|
||||
|
||||
arduinoStubMicros = startDeadlineUs;
|
||||
decoder.tick();
|
||||
assert(decoder.rxLineActive());
|
||||
|
||||
while (riseUs + badPeriodUs <= hardDeadlineUs)
|
||||
{
|
||||
emitEdge(decoder, riseUs + badPeriodUs / 2U, false);
|
||||
riseUs += badPeriodUs;
|
||||
emitEdge(decoder, riseUs, true);
|
||||
assert(decoder.preambleGoodPeriods == 0U);
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
}
|
||||
|
||||
arduinoStubMicros = hardDeadlineUs;
|
||||
decoder.tick();
|
||||
assert(decoder.rxLineActive());
|
||||
|
||||
arduinoStubMicros = riseUs + candidateTimeoutUs(decoder);
|
||||
decoder.tick();
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
|
||||
++arduinoStubMicros;
|
||||
decoder.tick();
|
||||
assert(!decoder.rxLineActive());
|
||||
assert(decoder.rxLastTerminal().reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(decoder.rxTerminalSeq() == 1U);
|
||||
}
|
||||
|
||||
void verifyCandidateExpiryWaitsForPipelineDrain()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t lastEdgeUs = 2000U;
|
||||
primeObservableCandidate(decoder, lastEdgeUs);
|
||||
decoder.pulseFilterHoldCount = 1U;
|
||||
|
||||
const uint32_t expiredAt = lastEdgeUs + candidateTimeoutUs(decoder) + 1U;
|
||||
decoder.expirePreambleCandidateIfIdle(expiredAt);
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
|
||||
decoder.pulseFilterHoldCount = 0U;
|
||||
decoder.expirePreambleCandidateIfIdle(expiredAt);
|
||||
assert(!decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 1U);
|
||||
}
|
||||
|
||||
void verifyCandidateExpiryAcrossMicrosWrap()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t lastEdgeUs = std::numeric_limits<uint32_t>::max() - 1000U;
|
||||
primeObservableCandidate(decoder, lastEdgeUs);
|
||||
|
||||
arduinoStubMicros = lastEdgeUs + candidateTimeoutUs(decoder) + 1U;
|
||||
decoder.tick();
|
||||
assert(decoder.rxLastTerminal().reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(!decoder.rxLineActive());
|
||||
}
|
||||
|
||||
void verifyCandidateTimeoutBoundary()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t lastEdgeUs = 2500U;
|
||||
primeObservableCandidate(decoder, lastEdgeUs);
|
||||
|
||||
arduinoStubMicros = lastEdgeUs + candidateTimeoutUs(decoder);
|
||||
decoder.tick();
|
||||
assert(decoder.rxLineActive());
|
||||
assert(decoder.rxTerminalSeq() == 0U);
|
||||
|
||||
++arduinoStubMicros;
|
||||
decoder.tick();
|
||||
assert(!decoder.rxLineActive());
|
||||
assert(decoder.rxLastTerminal().reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
}
|
||||
|
||||
void verifyTimedOutCandidateRestartIsTerminal()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
const uint32_t lastEdgeUs = 3000U;
|
||||
primeObservableCandidate(decoder, lastEdgeUs);
|
||||
|
||||
IR_DecoderRaw::FrontStorage nextEdge;
|
||||
nextEdge.time = lastEdgeUs + candidateTimeoutUs(decoder) + 1U;
|
||||
nextEdge.dir = true;
|
||||
decoder.preambleProcessEdge(nextEdge);
|
||||
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(terminal.seq == 1U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::CandidateTimeout);
|
||||
assert(decoder.preambleState == IR_DecoderRaw::PreambleState::Candidate);
|
||||
assert(decoder.preambleGoodPeriods == 0U);
|
||||
assert(decoder.rxReasonCounters()[
|
||||
static_cast<uint8_t>(IR_DecoderRaw::RxBriefReason::Preamble)] == 1U);
|
||||
}
|
||||
|
||||
void verifyLockedTimeoutPublishesHeaderTypeOnce()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
primeLocked(decoder, IR_MSG_DATA_NOACCEPT, 10U);
|
||||
decoder.lastEdgeTime = 5000U;
|
||||
|
||||
arduinoStubMicros = decoder.lastEdgeTime + decoderTimeoutUs(decoder) * 2U + 1U;
|
||||
decoder.tick();
|
||||
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(terminal.seq == 1U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::LockedTimeout);
|
||||
assert(terminal.msgType == IR_MSG_DATA_NOACCEPT);
|
||||
assert(terminal.hadLock);
|
||||
assert(!decoder.rxLineActive());
|
||||
|
||||
++arduinoStubMicros;
|
||||
decoder.tick();
|
||||
assert(decoder.rxTerminalSeq() == terminal.seq);
|
||||
}
|
||||
|
||||
void verifyDecodeAbortPublishesTerminal()
|
||||
{
|
||||
IR_DecoderRaw decoder(1U, 42U, nullptr);
|
||||
primeLocked(decoder, IR_MSG_REQUEST, 7U);
|
||||
decoder.isWrongPack = true;
|
||||
decoder.writeToBuffer(false);
|
||||
|
||||
const IR_RxTerminalInfo terminal = decoder.rxLastTerminal();
|
||||
assert(terminal.seq == 1U);
|
||||
assert(terminal.reason == IR_RxTerminalReason::DecodeAbort);
|
||||
assert(terminal.msgType == IR_MSG_REQUEST);
|
||||
assert(terminal.hadLock);
|
||||
}
|
||||
|
||||
void finishBackFrame(IR_DecoderRaw &decoder, bool corruptCrc)
|
||||
{
|
||||
constexpr uint8_t wireBytes = 5U;
|
||||
primeLocked(decoder, IR_MSG_BACK, wireBytes);
|
||||
|
||||
decoder.dataBuffer[1] = 0x12U;
|
||||
decoder.dataBuffer[2] = 0x34U;
|
||||
decoder.dataBuffer[3] = crc8Local(decoder.dataBuffer, 0U, 3U, poly1);
|
||||
const uint8_t crcLow = crc8Local(decoder.dataBuffer, 0U, 4U, poly2);
|
||||
const uint8_t finalBit = static_cast<uint8_t>((crcLow & 1U) ^ (corruptCrc ? 1U : 0U));
|
||||
decoder.dataBuffer[4] = static_cast<uint8_t>(crcLow & 0xFEU);
|
||||
decoder.i_dataBuffer = wireBytes * bitPerByte - 1U;
|
||||
decoder.bufBitPos = static_cast<int16_t>(decoder.i_dataBuffer);
|
||||
decoder.nextControlBit = 0xFFFFU;
|
||||
decoder.isData = true;
|
||||
decoder.writeToBuffer(finalBit != 0U);
|
||||
}
|
||||
|
||||
void verifyCompleteFrameTerminalReasons()
|
||||
{
|
||||
IR_DecoderRaw good(1U, 42U, nullptr);
|
||||
finishBackFrame(good, false);
|
||||
const IR_RxTerminalInfo ok = good.rxLastTerminal();
|
||||
assert(ok.seq == 1U);
|
||||
assert(ok.reason == IR_RxTerminalReason::FrameOk);
|
||||
assert(ok.msgType == IR_MSG_BACK);
|
||||
assert(ok.hadLock);
|
||||
|
||||
IR_DecoderRaw bad(1U, 42U, nullptr);
|
||||
finishBackFrame(bad, true);
|
||||
const IR_RxTerminalInfo crc = bad.rxLastTerminal();
|
||||
assert(crc.seq == 1U);
|
||||
assert(crc.reason == IR_RxTerminalReason::FrameCrcError);
|
||||
assert(crc.msgType == IR_MSG_BACK);
|
||||
assert(crc.hadLock);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
verifyInitialSnapshot();
|
||||
verifyCandidateExpiresOnIdleTick();
|
||||
verifyCandidateIdleExpiryThroughPublicPipeline();
|
||||
verifyCoarseResetPublishesThroughBatchedPublicPipeline();
|
||||
verifyFreshCandidateWithOnlyCoarseInvalidEdgesStaysActive();
|
||||
verifyCandidateExpiryWaitsForPipelineDrain();
|
||||
verifyCandidateExpiryAcrossMicrosWrap();
|
||||
verifyCandidateTimeoutBoundary();
|
||||
verifyTimedOutCandidateRestartIsTerminal();
|
||||
verifyLockedTimeoutPublishesHeaderTypeOnce();
|
||||
verifyDecodeAbortPublishesTerminal();
|
||||
verifyCompleteFrameTerminalReasons();
|
||||
std::cout << "IR RX terminal tests: OK\n";
|
||||
return 0;
|
||||
}
|
||||
97
tests/test_timing_contract.cpp
Normal file
97
tests/test_timing_contract.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "IR_Encoder.h"
|
||||
#include "IR_DecoderRaw.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
// Link seams: these paths are not exercised by the pure host timing test.
|
||||
bool IR_DecoderRaw::registerPairMuteEncoder(IR_Encoder *) { return true; }
|
||||
void IR_DecoderRaw::refreshPairMuteState() {}
|
||||
void IR_Encoder::send_HIGH(bool) {}
|
||||
void IR_Encoder::send_LOW() {}
|
||||
void IR_Encoder::send_EMPTY(uint8_t) {}
|
||||
|
||||
namespace
|
||||
{
|
||||
static_assert(irproto::dataWireBytes(0U) == 7U, "empty DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(3U) == 10U, "DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(24U) == 31U, "maximum DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(25U) == 0U, "oversized DATA must be rejected");
|
||||
static_assert(irproto::backWireBytes(1U) == 6U, "BACK wire size changed");
|
||||
static_assert(irproto::backWireBytes(26U) == 31U, "maximum BACK wire size changed");
|
||||
static_assert(irproto::backToWireBytes(24U) == 31U, "maximum BACK_TO wire size changed");
|
||||
|
||||
static_assert(irproto::wireLogicalTicks(6U) == 5472U, "6-byte tick count changed");
|
||||
static_assert(irproto::wireLogicalTicks(10U) == 8728U, "10-byte tick count changed");
|
||||
static_assert(irproto::wireLogicalTicks(31U) == 25822U, "31-byte tick count changed");
|
||||
static_assert(irproto::preambleAirtimeUsCeil() == 7737U, "preamble airtime changed");
|
||||
static_assert(irproto::trailingByteSyncAirtimeUsCeil() == 2922U,
|
||||
"trailing sync airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(6U) == 72000U, "6-byte airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(10U) == 114843U, "10-byte airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(31U) == 339764U, "31-byte airtime changed");
|
||||
static_assert(irproto::responseStartGuardUs() == 57198U, "response-start guard changed");
|
||||
static_assert(irproto::responseFrameGuardUs(6U) == 131100U, "response-frame guard changed");
|
||||
static_assert(irproto::completedFrameTerminalToNextPacketGuardUs(0U) == 44922U,
|
||||
"mandatory physical post-terminal quiet changed");
|
||||
static_assert(irproto::completedFrameTerminalToNextPacketGuardMsCeil(0U) == 45U,
|
||||
"mandatory post-terminal guard rounding changed");
|
||||
static_assert(irproto::completedFrameTerminalToNextPacketGuardMsCeil(60U) == 63U,
|
||||
"configured post-terminal guard changed");
|
||||
static_assert(irproto::completedFrameTerminalToNextPacketGuardMsCeil(65535U) ==
|
||||
65538U,
|
||||
"large guard must not wrap uint16");
|
||||
static_assert(IR_DecoderRaw::rxMaxPackSize() == 31U, "RX max must be the wire max");
|
||||
|
||||
uint32_t sumLogicalTicks(const IrTxGateRun *runs, size_t count)
|
||||
{
|
||||
uint32_t total = 0U;
|
||||
for (size_t i = 0U; i < count; ++i)
|
||||
total += runs[i].lenTicks;
|
||||
return total;
|
||||
}
|
||||
|
||||
void verifyFormulaAgainstTxFsm()
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> frame{};
|
||||
std::array<IrTxGateRun, 1024U> runs{};
|
||||
|
||||
for (uint8_t wireBytes = 1U; wireBytes <= irproto::kMaxWireFrameBytes; ++wireBytes)
|
||||
{
|
||||
for (uint8_t pattern = 0U; pattern < 4U; ++pattern)
|
||||
{
|
||||
for (uint8_t i = 0U; i < wireBytes; ++i)
|
||||
{
|
||||
frame[i] = pattern == 0U ? 0x00U
|
||||
: pattern == 1U ? 0xFFU
|
||||
: pattern == 2U ? static_cast<uint8_t>((i & 1U) ? 0x55U : 0xAAU)
|
||||
: static_cast<uint8_t>(i * 73U + 19U);
|
||||
}
|
||||
const size_t count = IR_Encoder::buildGateRuns(
|
||||
frame.data(), wireBytes, runs.data(), runs.size());
|
||||
assert(count != 0U);
|
||||
assert(sumLogicalTicks(runs.data(), count) == irproto::wireLogicalTicks(wireBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verifyPublicSendTimeResults()
|
||||
{
|
||||
IR_Encoder encoder(1U, 42U, nullptr, false);
|
||||
uint8_t payload[26]{};
|
||||
|
||||
assert(encoder.testSendAccept(1U) == 72U);
|
||||
assert(encoder.testSendTime(1U, payload, 3U) == 115U);
|
||||
assert(encoder.testSendBack(payload, 26U) == 340U);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
verifyFormulaAgainstTxFsm();
|
||||
verifyPublicSendTimeResults();
|
||||
std::cout << "IR timing contract tests: OK\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user