Files
IR-protocol/IR_Encoder.cpp

1537 lines
46 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "IR_Encoder.h"
#include "IR_DecoderRaw.h"
#include "IrTxIsrBufferedStorage.h"
#include <string.h>
#if defined(_MSC_VER)
#define IRPROTO_PRAGMA_MESSAGE(text) __pragma(message(text))
#else
#define IRPROTO_PRAGMA_MESSAGE(text) _Pragma(#text)
#endif
#if defined(ARDUINO_ARCH_STM32)
#if defined(STM32G4xx)
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR + built-in DMA"))
#elif defined(STM32F4xx)
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR only"))
#else
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR"))
#endif
#endif
#define LoopOut 12
#define ISR_Out 10
#define TestOut 13
IR_Encoder *IR_Encoder::head = nullptr;
IR_Encoder *IR_Encoder::last = nullptr;
volatile bool IR_Encoder::carrierStopPending = false;
IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool autoHandle)
{
setPin(pin);
id = addr;
txIsrMode_ = txIsrLegacyMode_ ? TxIsrMode::Legacy : TxIsrMode::Buffered;
this->decPair = decPair;
if (decPair != nullptr)
{
singleBlindDecoder = decPair;
blindDecoders = &singleBlindDecoder;
decodersCount = 1;
decPair->encoder = this;
}
registerWithBlindDecoders();
if (autoHandle)
{
if (IR_Encoder::head == nullptr)
{
IR_Encoder::head = this;
}
if (last != nullptr)
{
last->next = this;
}
last = this;
pinMode(pin, OUTPUT);
}
powerNumerator_ = 1;
}
HardwareTimer* IR_Encoder::IR_Timer = nullptr;
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
IR_Encoder::ExternalTxStartFnV2 IR_Encoder::externalTxStartFnV2 = nullptr;
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
void *IR_Encoder::externalTxCtx = nullptr;
bool IR_Encoder::txIsrLegacyMode_ = true;
uint16_t IR_Encoder::s_carrierMultiply = 2;
const char* irSendStatusToString(IR_SendStatus status)
{
switch (status)
{
case IR_SendStatus::Success:
return "Success";
case IR_SendStatus::PayloadTooLarge:
return "PayloadTooLarge";
case IR_SendStatus::EncoderBusy:
return "EncoderBusy";
case IR_SendStatus::BufferTooLarge:
return "BufferTooLarge";
case IR_SendStatus::ExternalBackendBusy:
return "ExternalBackendBusy";
case IR_SendStatus::ExternalStartFailed:
return "ExternalStartFailed";
case IR_SendStatus::ExternalNoStream:
return "ExternalNoStream";
case IR_SendStatus::ExternalInvalidConfig:
return "ExternalInvalidConfig";
case IR_SendStatus::BuildGateRunsFailed:
return "BuildGateRunsFailed";
case IR_SendStatus::ScaleGateRunsFailed:
return "ScaleGateRunsFailed";
case IR_SendStatus::DmaStartFailed:
return "DmaStartFailed";
case IR_SendStatus::EncoderPinUnavailable:
return "EncoderPinUnavailable";
case IR_SendStatus::BufferedStorageInvalid:
return "BufferedStorageInvalid";
case IR_SendStatus::InvalidArgument:
return "InvalidArgument";
case IR_SendStatus::TimingOverflow:
return "TimingOverflow";
case IR_SendStatus::PlanMismatch:
return "PlanMismatch";
case IR_SendStatus::DmaTransferError:
return "DmaTransferError";
case IR_SendStatus::DmaStalled:
return "DmaStalled";
default:
return "Unknown";
}
}
void IR_Encoder::setCarrierMultiply(uint16_t multiply)
{
if (multiply < 2)
{
multiply = 2;
}
s_carrierMultiply = multiply;
}
uint16_t IR_Encoder::carrierMultiply()
{
return s_carrierMultiply;
}
void IR_Encoder::retuneCarrierClock()
{
if (IR_Timer == nullptr)
{
return;
}
IR_Timer->pause();
IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->pause();
}
uint16_t IR_Encoder::maxPowerNumerator()
{
return static_cast<uint16_t>(s_carrierMultiply / 2U);
}
void IR_Encoder::setPowerNumerator(uint16_t n)
{
const uint16_t cap = maxPowerNumerator();
powerNumerator_ = (n > cap) ? cap : n;
}
void IR_Encoder::setPowerPercent(uint8_t p)
{
if (p > 100U)
{
p = 100U;
}
const uint16_t cap = maxPowerNumerator();
const uint32_t n = ((uint32_t)p * (uint32_t)cap + 50U) / 100U;
powerNumerator_ = static_cast<uint16_t>(n);
}
uint16_t IR_Encoder::powerNumerator() const
{
return powerNumerator_;
}
bool IR_Encoder::scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply)
{
if (runs == nullptr || ioCount == nullptr || maxRuns == 0)
{
return false;
}
if (multiply < 2)
{
multiply = 2;
}
const size_t nIn = *ioCount;
if (nIn > maxRuns)
{
return false;
}
// First determine the exact output size without touching the caller's
// data. A physical run can split only at uint16_t storage boundaries.
// The second pass walks backwards, so expanded output never overwrites an
// input run that has not been consumed yet. This keeps the helper fully
// in-place instead of reserving several kilobytes of temporary stack.
uint64_t logicalBoundary = 0U;
uint64_t physicalBoundary = 0U;
size_t outputCount = 0U;
for (size_t r = 0; r < nIn; r++)
{
if (runs[r].lenTicks == 0U ||
logicalBoundary > UINT64_MAX - runs[r].lenTicks)
{
return false;
}
logicalBoundary += runs[r].lenTicks;
if (logicalBoundary > (UINT64_MAX - 1U) / multiply)
{
return false;
}
const uint64_t nextPhysicalBoundary =
(logicalBoundary * static_cast<uint64_t>(multiply) + 1U) / 2U;
const uint64_t physicalLen = nextPhysicalBoundary - physicalBoundary;
physicalBoundary = nextPhysicalBoundary;
const uint64_t chunks = (physicalLen + 65534U) / 65535U;
if (chunks > static_cast<uint64_t>(maxRuns - outputCount))
{
return false;
}
outputCount += static_cast<size_t>(chunks);
}
size_t write = outputCount;
uint64_t logicalEnd = logicalBoundary;
uint64_t physicalEnd = physicalBoundary;
for (size_t r = nIn; r != 0U; --r)
{
const IR_TxGateRun input = runs[r - 1U];
const uint64_t logicalStart = logicalEnd - input.lenTicks;
const uint64_t physicalStart =
(logicalStart * static_cast<uint64_t>(multiply) + 1U) / 2U;
uint64_t physicalLen = physicalEnd - physicalStart;
uint64_t chunks = (physicalLen + 65534U) / 65535U;
while (chunks != 0U)
{
// We are writing backwards: emit the final (possibly short)
// chunk first, then full-sized chunks before it.
const uint64_t chunk64 = physicalLen - (chunks - 1U) * 65535U;
runs[--write].lenTicks = static_cast<uint16_t>(chunk64);
runs[write].gate = input.gate;
physicalLen -= chunk64;
--chunks;
}
logicalEnd = logicalStart;
physicalEnd = physicalStart;
}
*ioCount = outputCount;
return true;
}
void IR_Encoder::setTxIsrLegacyMode(bool legacy)
{
txIsrLegacyMode_ = legacy;
const TxIsrMode mode = legacy ? TxIsrMode::Legacy : TxIsrMode::Buffered;
for (IR_Encoder *p = head; p != nullptr; p = p->next)
{
p->txIsrMode_ = mode;
}
}
bool IR_Encoder::txIsrLegacyMode()
{
return txIsrLegacyMode_;
}
void IR_Encoder::attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage)
{
txBufferedCtx_ = &storage;
}
void IR_Encoder::detachBufferedIsrStorage()
{
txBufferedCtx_ = nullptr;
if (!isSending)
{
txActiveBufferedCtx_ = nullptr;
txUseBufferedIsr_ = false;
}
}
bool IR_Encoder::hasBufferedIsrStorage() const
{
return txBufferedCtx_ != nullptr && txBufferedCtx_->isValid();
}
void IR_Encoder::enableBufferedIsr(IrTxIsrBufferedStorageBase& storage)
{
attachBufferedIsrStorage(storage);
txIsrMode_ = TxIsrMode::Buffered;
}
void IR_Encoder::disableBufferedIsr()
{
txIsrMode_ = TxIsrMode::Legacy;
if (!isSending)
{
txActiveBufferedCtx_ = nullptr;
txUseBufferedIsr_ = false;
}
}
IR_Encoder::TxIsrMode IR_Encoder::txIsrMode() const
{
return txIsrMode_;
}
bool IR_Encoder::shouldUseBufferedIsr() const
{
return txIsrMode_ == TxIsrMode::Buffered &&
txBufferedCtx_ != nullptr &&
txBufferedCtx_->isValid();
}
bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal)
{
while (true)
{
switch (st.signal)
{
case noSignal:
st.signal = preamb;
return false;
case preamb:
if (st.preambFrontCounter)
{
st.preambFrontCounter--;
st.toggleCounter = preambToggle;
st.state = !st.state;
return true;
}
st.signal = data;
st.state = !LOW;
continue;
case data:
if (st.dataSequenceCounter)
{
if (!(st.dataSequenceCounter & 1U))
{
st.currentBitSequence =
((sendBufferLocal[st.dataByteCounter] >> st.dataBitCounter) & 1U) ? bitHigh : bitLow;
st.dataBitCounter--;
}
st.toggleCounter = st.currentBitSequence[!st.state];
st.dataSequenceCounter--;
st.state = !st.state;
return true;
}
st.syncLastBit = ((sendBufferLocal[st.dataByteCounter]) & 1U);
st.dataByteCounter++;
st.dataBitCounter = bitPerByte - 1;
st.dataSequenceCounter = bitPerByte * 2;
st.signal = sync;
continue;
case sync:
if (st.syncSequenceCounter)
{
if (!(st.syncSequenceCounter & 1U))
{
if (st.syncSequenceCounter == 2)
{
st.currentBitSequence = ((sendBufferLocal[st.dataByteCounter]) & 0b10000000) ? bitLow : bitHigh;
}
else
{
st.currentBitSequence = st.syncLastBit ? bitLow : bitHigh;
st.syncLastBit = !st.syncLastBit;
}
}
st.toggleCounter = st.currentBitSequence[!st.state];
st.syncSequenceCounter--;
st.state = !st.state;
return true;
}
st.signal = data;
st.syncSequenceCounter = syncBits * 2;
if (st.dataByteCounter >= st.sendLen)
{
st.signal = noSignal;
}
continue;
default:
return false;
}
}
}
bool IR_Encoder::txAdvanceAfterOutput(TxFsmState &st, const uint8_t *sendBufferLocal)
{
if (st.toggleCounter)
{
st.toggleCounter--;
return true;
}
return txAdvanceBoundary(st, sendBufferLocal);
}
bool IR_Encoder::txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut)
{
gateOut = st.state;
return txAdvanceAfterOutput(st, sendBufferLocal);
}
IR_Encoder::TxFsmState IR_Encoder::initialTxFsm(uint8_t len)
{
TxFsmState st{};
st.sendLen = len;
st.toggleCounter = preambToggle;
st.dataBitCounter = bitPerByte - 1;
st.dataByteCounter = 0;
st.preambFrontCounter = preambPulse * 2 - 1;
st.dataSequenceCounter = bitPerByte * 2;
st.syncSequenceCounter = syncBits * 2;
st.syncLastBit = false;
st.signal = preamb;
st.state = HIGH;
st.currentBitSequence = bitHigh;
return st;
}
void IR_Encoder::loadTxFsmFromMembers(TxFsmState &st) const
{
st.sendLen = sendLen;
st.toggleCounter = toggleCounter;
st.dataBitCounter = dataBitCounter;
st.dataByteCounter = dataByteCounter;
st.preambFrontCounter = preambFrontCounter;
st.dataSequenceCounter = dataSequenceCounter;
st.syncSequenceCounter = syncSequenceCounter;
st.syncLastBit = syncLastBit;
st.state = state;
st.currentBitSequence = currentBitSequence;
st.signal = signal;
}
void IR_Encoder::storeTxFsmToMembers(const TxFsmState &st)
{
sendLen = st.sendLen;
toggleCounter = st.toggleCounter;
dataBitCounter = st.dataBitCounter;
dataByteCounter = st.dataByteCounter;
preambFrontCounter = st.preambFrontCounter;
dataSequenceCounter = st.dataSequenceCounter;
syncSequenceCounter = st.syncSequenceCounter;
syncLastBit = st.syncLastBit;
state = st.state;
currentBitSequence = st.currentBitSequence;
signal = st.signal;
}
inline HardwareTimer* IR_Encoder::get_IR_Timer(){return IR_Encoder::IR_Timer;}
void IR_Encoder::carrierResume() {
if (IR_Timer != nullptr)
IR_Timer->resume();
}
void IR_Encoder::carrierPauseIfIdle() {
for (IR_Encoder *p = head; p != nullptr; p = p->next)
if (p->isSending)
return;
if (IR_Timer != nullptr)
IR_Timer->pause();
}
void IR_Encoder::tick() {
if (!carrierStopPending)
return;
carrierStopPending = false;
carrierPauseIfIdle();
}
void IR_Encoder::begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)()){
IR_Timer = timer;
if(IR_Timer == nullptr) return;
IR_Timer->pause();
IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->attachInterrupt(channel, (isrCallback == nullptr ? IR_Encoder::isr : isrCallback));
NVIC_SetPriority(IRQn, priority);
IR_Timer->pause();
}
void IR_Encoder::beginClockOnly(HardwareTimer *timer)
{
IR_Timer = timer;
if (IR_Timer == nullptr)
return;
IR_Timer->pause();
IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->pause();
}
void IR_Encoder::setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx)
{
externalTxStartFn = startFn;
externalTxStartFnV2 = nullptr;
externalTxBusyFn = busyFn;
externalTxCtx = ctx;
}
void IR_Encoder::setExternalTxBackendV2(ExternalTxStartFnV2 startFn, ExternalTxBusyFn busyFn, void *ctx)
{
externalTxStartFn = nullptr;
externalTxStartFnV2 = startFn;
externalTxBusyFn = busyFn;
externalTxCtx = ctx;
}
void IR_Encoder::externalFinishSend()
{
externalFinishSend(txOperationId_, IR_SendStatus::Success);
}
void IR_Encoder::externalFinishSend(uint32_t operationId, IR_SendStatus terminalStatus)
{
if (!isSending || operationId == 0U || operationId != txOperationId_)
return;
// Force output low.
if (port != nullptr) {
port->BSRR = ((uint32_t)mask) << 16;
}
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, terminalStatus);
}
uint32_t IR_Encoder::beginTxOperation(const IR_TxPlan& plan)
{
uint32_t operationId = txNextOperationId_ + 1U;
if (operationId == 0U)
operationId = 1U;
txNextOperationId_ = operationId;
txRecordVersion_++;
txOperationId_ = operationId;
txState_ = IR_TxState::Preparing;
txTerminalStatus_ = IR_SendStatus::Success;
txMultiplySnap_ = plan.carrierMultiply;
txPlannedPhysicalTicks_ = plan.physicalTicks;
txPlannedAirtimeUs_ = plan.airtimeUs;
txClockBasis_ = plan.clockBasis;
txAcceptedAtUs_ = micros();
txArmedAtUs_ = 0U;
txTerminalAtUs_ = 0U;
txRecordVersion_++;
return operationId;
}
void IR_Encoder::markTxArmed(uint32_t operationId)
{
if (operationId == 0U || operationId != txOperationId_ || txState_ != IR_TxState::Preparing)
return;
txRecordVersion_++;
txArmedAtUs_ = micros();
txState_ = IR_TxState::Transmitting;
txRecordVersion_++;
}
bool IR_Encoder::finishTxOperation(uint32_t operationId, IR_SendStatus terminalStatus)
{
if (operationId == 0U || operationId != txOperationId_)
return false;
if (txState_ != IR_TxState::Preparing && txState_ != IR_TxState::Transmitting)
return false;
txRecordVersion_++;
txTerminalStatus_ = terminalStatus;
txTerminalAtUs_ = micros();
txState_ = terminalStatus == IR_SendStatus::Success ? IR_TxState::Completed : IR_TxState::Failed;
txRecordVersion_++;
return true;
}
IR_TxSnapshot IR_Encoder::txSnapshot() const
{
IR_TxSnapshot snapshot;
uint8_t before = 0U;
uint8_t after = 0U;
do
{
before = txRecordVersion_;
if ((before & 1U) != 0U)
continue;
snapshot.operationId = txOperationId_;
snapshot.state = txState_;
snapshot.status = txTerminalStatus_;
snapshot.carrierMultiply = txMultiplySnap_;
snapshot.clockBasis = txClockBasis_;
snapshot.plannedPhysicalTicks = txPlannedPhysicalTicks_;
snapshot.plannedAirtimeUs = txPlannedAirtimeUs_;
snapshot.acceptedAtUs = txAcceptedAtUs_;
snapshot.armedAtUs = txArmedAtUs_;
snapshot.terminalAtUs = txTerminalAtUs_;
after = txRecordVersion_;
} while (before != after || (after & 1U) != 0U);
return snapshot;
}
bool IR_Encoder::isOperationTerminal(uint32_t operationId) const
{
if (operationId == 0U)
return false;
const IR_TxSnapshot snapshot = txSnapshot();
return snapshot.operationId == operationId && snapshot.terminal();
}
bool IR_Encoder::isOperationComplete(uint32_t operationId) const
{
if (operationId == 0U)
return false;
const IR_TxSnapshot snapshot = txSnapshot();
return snapshot.operationId == operationId && snapshot.state == IR_TxState::Completed;
}
size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns)
{
if (packet == nullptr || outRuns == nullptr || maxRuns == 0)
{
return 0;
}
if (len == 0 || len > irproto::kMaxWireFrameBytes)
{
return 0;
}
// Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
uint8_t sendBufferLocal[irproto::kMaxWireFrameBytes] = {0};
memcpy(sendBufferLocal, packet, len);
TxFsmState st = initialTxFsm(len);
size_t runCount = 0;
bool isActive = true;
while (isActive)
{
bool gate = false;
isActive = txEmitTick(st, sendBufferLocal, gate);
if (runCount > 0 && outRuns[runCount - 1].gate == gate)
{
outRuns[runCount - 1].lenTicks = (uint16_t)(outRuns[runCount - 1].lenTicks + 1U);
}
else
{
if (runCount >= maxRuns)
{
return 0;
}
outRuns[runCount].gate = gate;
outRuns[runCount].lenTicks = 1U;
runCount++;
}
}
return runCount;
}
size_t IR_Encoder::buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply)
{
if (outRuns == nullptr || maxRuns == 0U)
return 0U;
const IR_TxPlan plan = buildPhysicalTransmission(packet, len, outRuns, maxRuns, multiply);
return plan.valid() ? static_cast<size_t>(plan.gateRunCount) : 0U;
}
IR_TxPlan IR_Encoder::buildPhysicalPlan(const uint8_t *packet,
uint8_t len,
IR_TxGateRun *outRuns,
size_t maxRuns,
uint16_t multiply,
bool emitRuns)
{
IR_TxPlan plan;
if (packet == nullptr || len == 0U)
{
plan.status = IR_SendStatus::InvalidArgument;
return plan;
}
if (len > irproto::kMaxWireFrameBytes)
{
plan.status = IR_SendStatus::BufferTooLarge;
return plan;
}
if (emitRuns && (outRuns == nullptr || maxRuns == 0U))
{
plan.status = IR_SendStatus::InvalidArgument;
return plan;
}
if (multiply < 2U)
multiply = 2U;
plan.wireBytes = len;
plan.carrierMultiply = multiply;
plan.clockBasis = IR_TxClockBasis::Nominal;
plan.tickClockHz = static_cast<uint32_t>(carrierFrec) * static_cast<uint32_t>(multiply);
plan.tickDivider = 1U;
uint8_t sendBufferLocal[irproto::kMaxWireFrameBytes] = {0};
memcpy(sendBufferLocal, packet, len);
TxFsmState st = initialTxFsm(len);
uint64_t logicalBoundary = 0U;
uint64_t physicalBoundary = 0U;
uint32_t runCount = 0U;
bool capacityExceeded = false;
auto appendPhysicalRun = [&](bool gate, uint32_t logicalLen) -> bool {
if (logicalLen == 0U)
return true;
logicalBoundary += logicalLen;
// One logical tick is 1/(2*carrierFrec). Cumulative ceil preserves
// the exact rational phase for both even and odd multiply values.
const uint64_t nextPhysicalBoundary =
(logicalBoundary * static_cast<uint64_t>(multiply) + 1U) / 2U;
uint64_t physicalLen = nextPhysicalBoundary - physicalBoundary;
physicalBoundary = nextPhysicalBoundary;
while (physicalLen != 0U)
{
if (runCount == UINT32_MAX)
return false;
const uint16_t chunk = static_cast<uint16_t>(
physicalLen > 65535U ? 65535U : physicalLen);
if (emitRuns && static_cast<size_t>(runCount) < maxRuns)
{
outRuns[runCount].gate = gate;
outRuns[runCount].lenTicks = chunk;
}
else if (emitRuns)
{
capacityExceeded = true;
}
++runCount;
physicalLen -= chunk;
}
return true;
};
bool currentGate = false;
uint32_t currentLogicalLen = 0U;
bool havePendingRun = false;
bool isActive = true;
while (isActive)
{
bool gate = false;
isActive = txEmitTick(st, sendBufferLocal, gate);
if (!havePendingRun)
{
currentGate = gate;
currentLogicalLen = 1U;
havePendingRun = true;
}
else if (currentGate == gate)
{
++currentLogicalLen;
}
else
{
if (!appendPhysicalRun(currentGate, currentLogicalLen))
{
plan.status = IR_SendStatus::TimingOverflow;
return plan;
}
currentGate = gate;
currentLogicalLen = 1U;
}
}
if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen))
{
plan.status = IR_SendStatus::TimingOverflow;
return plan;
}
if (physicalBoundary > UINT32_MAX)
{
plan.status = IR_SendStatus::TimingOverflow;
return plan;
}
plan.physicalTicks = static_cast<uint32_t>(physicalBoundary);
plan.gateRunCount = runCount;
if (capacityExceeded)
{
plan.status = IR_SendStatus::BuildGateRunsFailed;
return plan;
}
plan.status = IR_SendStatus::Success;
if (!calculateAirtimeUs(plan))
plan.status = IR_SendStatus::TimingOverflow;
return plan;
}
bool IR_Encoder::calculateAirtimeUs(IR_TxPlan& plan)
{
if (plan.tickClockHz == 0U || plan.tickDivider == 0U)
return false;
auto gcd64 = [](uint64_t a, uint64_t b) -> uint64_t {
while (b != 0U)
{
const uint64_t next = a % b;
a = b;
b = next;
}
return a;
};
uint64_t a = plan.physicalTicks;
uint64_t b = plan.tickDivider;
uint64_t c = 1000000U;
uint64_t denominator = plan.tickClockHz;
uint64_t divisor = gcd64(a, denominator);
a /= divisor;
denominator /= divisor;
divisor = gcd64(b, denominator);
b /= divisor;
denominator /= divisor;
divisor = gcd64(c, denominator);
c /= divisor;
denominator /= divisor;
const uint64_t max64 = ~static_cast<uint64_t>(0U);
if ((b != 0U && a > max64 / b) ||
(c != 0U && a * b > max64 / c))
return false;
const uint64_t numerator = a * b * c;
uint64_t duration = numerator / denominator;
if ((numerator % denominator) != 0U)
++duration;
if (duration > UINT32_MAX)
return false;
plan.airtimeUs = static_cast<uint32_t>(duration);
return true;
}
bool IR_Encoder::applyTickClock(IR_TxPlan& plan,
uint32_t clockNumeratorHz,
uint32_t clockDivider,
IR_TxClockBasis basis)
{
if (!plan.valid() || clockNumeratorHz == 0U || clockDivider == 0U)
return false;
plan.clockBasis = basis;
plan.tickClockHz = clockNumeratorHz;
plan.tickDivider = clockDivider;
if (!calculateAirtimeUs(plan))
{
plan.status = IR_SendStatus::TimingOverflow;
return false;
}
return true;
}
void IR_Encoder::applyConfiguredTimerClock(IR_TxPlan& plan)
{
if (!plan.valid() || IR_Timer == nullptr)
return;
const uint32_t timerClockHz = IR_Timer->getTimerClkFreq();
const uint64_t divider = static_cast<uint64_t>(IR_Timer->getPrescaleFactor()) *
static_cast<uint64_t>(IR_Timer->getOverflow(TICK_FORMAT));
if (timerClockHz == 0U || divider == 0U || divider > UINT32_MAX)
{
plan.status = IR_SendStatus::TimingOverflow;
return;
}
applyTickClock(plan, timerClockHz, static_cast<uint32_t>(divider),
IR_TxClockBasis::ConfiguredTimer);
}
IR_TxPlan IR_Encoder::planPhysicalTransmission(const uint8_t *packet, uint8_t len, uint16_t multiply)
{
return buildPhysicalPlan(packet, len, nullptr, 0U, multiply, false);
}
IR_TxPlan IR_Encoder::buildPhysicalTransmission(const uint8_t *packet,
uint8_t len,
IR_TxGateRun *outRuns,
size_t maxRuns,
uint16_t multiply)
{
return buildPhysicalPlan(packet, len, outRuns, maxRuns, multiply, true);
}
IR_TxPlan IR_Encoder::planTransmission(const uint8_t *packet, uint8_t len) const
{
IR_TxPlan plan = planPhysicalTransmission(packet, len, carrierMultiply());
applyConfiguredTimerClock(plan);
return plan;
}
void IR_Encoder::enable()
{
bool exist = false;
IR_Encoder *current = IR_Encoder::head;
while (current != nullptr)
{
exist = (current == this);
if (exist) break;
current = current->next;
}
if (!exist)
{
if (IR_Encoder::head == nullptr)
{
IR_Encoder::head = this;
last = this;
}
else
{
last->next = this;
last = this;
}
this->next = nullptr; // Указываем, что следующий за этим элементом — nullptr
}
pinMode(pin, OUTPUT);
}
void IR_Encoder::disable()
{
IR_Encoder *current = IR_Encoder::head;
IR_Encoder *prev = nullptr;
while (current != nullptr)
{
if (current == this) break;
prev = current;
current = current->next;
}
if (current != nullptr) // Элемент найден в списке
{
if (prev != nullptr)
{
prev->next = current->next; // Убираем текущий элемент из списка
}
else
{
IR_Encoder::head = current->next; // Удаляемый элемент был первым
}
if (current == last)
{
last = prev; // Если удаляется последний элемент, обновляем last
}
}
pinMode(pin, INPUT);
}
void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
{
if (count > IR_PAIR_MUTE_MAX_ENCODERS)
{
decodersCount = 0;
blindDecoders = nullptr;
return;
}
decodersCount = count;
blindDecoders = decoders;
registerWithBlindDecoders();
refreshBlindDecoderMuteState();
}
IR_Encoder::~IR_Encoder(){}
IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept)
{
return sendData(addrTo, &dataByte, 1, needAccept);
}
IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept){
return sendDataFULL(id, addrTo, data, len, needAccept);
}
IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept)
{
// 5-битное поле длины => ВЕСЬ кадр ≤31 байт (для Data payload ≤24). Было `len > bytePerPack(31)` —
// неверно: packSize=7+len оборачивался в заголовке (packSize & 0x1F) при len 25..31 → кадр молча
// терялся, а send возвращал успех. Проверяем полный packSize в широком типе (uint8_t 7+len мог переполниться).
if (len > irproto::kMaxDataPayloadBytes)
{
Serial.println("IR Pack to big");
return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
}
if (len != 0U && data == nullptr)
return IR_SendResult(false, 0, IR_SendStatus::InvalidArgument);
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
memset(sendBuffer, 0x00, irproto::kMaxWireFrameBytes);
uint8_t packSize = msgBytes + addrBytes + addrBytes + len + crcBytes;
uint8_t msgType =
((needAccept ? IR_MSG_DATA_ACCEPT : IR_MSG_DATA_NOACCEPT) << 5) | (packSize & IR_MASK_MSG_INFO);
// формирование массива
// msg_type
sendBuffer[0] = msgType;
// addr_self
sendBuffer[1] = addrFrom >> 8 & 0xFF;
sendBuffer[2] = addrFrom & 0xFF;
// addr_to
sendBuffer[3] = addrTo >> 8 & 0xFF;
sendBuffer[4] = addrTo & 0xFF;
for (uint16_t i = dataStart; (i < dataStart + len) && (data != nullptr); i++)
{
sendBuffer[i] = ((uint8_t *)data)[i - dataStart];
}
// data crc
sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;
//* вывод итогового буфера
// Serial.print("IR SEND [len=");
// Serial.print(packSize);
// Serial.print("] : ");
// for (uint8_t i = 0; i < packSize; i++)
// {
// if (sendBuffer[i] < 0x10)
// Serial.print('0');
// Serial.print(sendBuffer[i], HEX);
// Serial.print(' ');
// }
// Serial.println();
// if (decPair != nullptr) {
// decPair->isWaitingAccept = ((msgType >> 5) & IR_MASK_MSG_TYPE == IR_MSG_DATA_ACCEPT);
// if (decPair->isWaitingAccept) {
// decPair->addrWaitingFrom = addrTo;
// }
// }
// отправка
return rawSendTracked(sendBuffer, packSize);
}
IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
{
(void)addrTo;
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
memset(sendBuffer, 0x00, irproto::kMaxWireFrameBytes);
sendBuffer[0] = IR_MSG_ACCEPT << 5;
sendBuffer[0] |= packsize & IR_MASK_MSG_INFO; // размер пакета
// addr_self
sendBuffer[1] = id >> 8 & 0xFF;
sendBuffer[2] = id & 0xFF;
// Serial.print("\nRAW Accept to ");
// Serial.println(addrTo);
sendBuffer[3] = customByte;
// data crc
sendBuffer[4] = crc8(sendBuffer, 0, 4, poly1) & 0xFF;
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF;
return rawSendTracked(sendBuffer, packsize);
}
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
{
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
memset(sendBuffer, 0x00, irproto::kMaxWireFrameBytes);
sendBuffer[0] = IR_MSG_REQUEST << 5;
sendBuffer[0] |= packsize & IR_MASK_MSG_INFO;
// addr_self
sendBuffer[1] = id >> 8 & 0xFF;
sendBuffer[2] = id & 0xFF;
// addr_to
sendBuffer[3] = addrTo >> 8 & 0xFF;
sendBuffer[4] = addrTo & 0xFF;
// data crc
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly1) & 0xFF;
sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF;
return rawSendTracked(sendBuffer, packsize);
}
IR_SendResult IR_Encoder::sendBack(uint8_t data)
{
return _sendBack(false, 0, &data, 1);
}
IR_SendResult IR_Encoder::sendBack(uint8_t *data, uint8_t len)
{
return _sendBack(false, 0, data, len);
}
IR_SendResult IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len)
{
return _sendBack(true, addrTo, data, len);
}
IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
{
// Длина = ВЕСЬ кадр в 5 битах (≤31). Проверяем полный packSize. Было `len>bytePerPack` + `min(1,len)`:
// многобайтовый back (speed + customBackData) слался ОБРЕЗАННЫМ — packSize считал лишь 1 байт данных,
// остальные не влезали в кадр и затирались CRC. Теперь учитываем полный len.
const uint8_t payloadLimit = isAdressed ? irproto::kMaxBackToPayloadBytes
: irproto::kMaxBackPayloadBytes;
if (len > payloadLimit)
return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
if (len != 0U && data == nullptr)
return IR_SendResult(false, 0, IR_SendStatus::InvalidArgument);
memset(sendBuffer, 0x00, irproto::kMaxWireFrameBytes);
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + len + crcBytes;
uint8_t msgType =
((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & IR_MASK_MSG_INFO);
// формирование массива
// msg_type
sendBuffer[0] = msgType;
// addr_from or data
sendBuffer[1] = id >> 8 & 0xFF;
sendBuffer[2] = id & 0xFF;
// addr_to
sendBuffer[3] = addrTo >> 8 & 0xFF;
sendBuffer[4] = addrTo & 0xFF;
for (uint16_t i = dataStart; i < dataStart + len; i++)
{
sendBuffer[i] = ((uint8_t *)data)[i - dataStart];
}
// data crc
sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;
// отправка
return rawSendTracked(sendBuffer, packSize);
}
void IR_Encoder::registerWithBlindDecoders()
{
if (!decodersCount || blindDecoders == nullptr)
return;
for (uint8_t i = 0; i < decodersCount; i++)
{
if (blindDecoders[i] != nullptr)
blindDecoders[i]->registerPairMuteEncoder(this);
}
}
void IR_Encoder::refreshBlindDecoderMuteState()
{
if (!decodersCount || blindDecoders == nullptr)
return;
for (uint8_t i = 0; i < decodersCount; i++)
{
if (blindDecoders[i] != nullptr)
blindDecoders[i]->refreshPairMuteState();
}
}
IR_SendStatus IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
{
return rawSendTracked(ptr, len).status;
}
IR_SendResult IR_Encoder::rawSendTracked(uint8_t *ptr, uint8_t len)
{
if (isSending)
return IR_SendResult(false, 0U, IR_SendStatus::EncoderBusy);
if (ptr == nullptr || len == 0U)
return IR_SendResult(false, 0U, IR_SendStatus::InvalidArgument);
IR_TxPlan plan = planTransmission(ptr, len);
if (!plan.valid())
return IR_SendResult(false, 0U, plan.status, 0U, plan.airtimeUs, plan.clockBasis);
const bool hasExternalBackend = externalTxStartFnV2 != nullptr || externalTxStartFn != nullptr;
if (hasExternalBackend)
{
if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
return IR_SendResult(false, 0U, IR_SendStatus::ExternalBackendBusy,
0U, plan.airtimeUs, plan.clockBasis);
sendLen = len;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
isSending = true;
const uint32_t operationId = beginTxOperation(plan);
refreshBlindDecoderMuteState();
const IR_SendStatus status = externalTxStartFnV2 != nullptr
? externalTxStartFnV2(externalTxCtx, this, ptr, len, plan, operationId)
: externalTxStartFn(externalTxCtx, this, ptr, len);
if (status != IR_SendStatus::Success)
{
isSending = false;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, status);
return IR_SendResult(false, 0U, status, operationId,
plan.airtimeUs, plan.clockBasis);
}
markTxArmed(operationId);
return IR_SendResult(true, plan.airtimeMsCeil(), status, operationId,
plan.airtimeUs, plan.clockBasis);
}
if (port == nullptr || mask == 0)
return IR_SendResult(false, 0U, IR_SendStatus::EncoderPinUnavailable,
0U, plan.airtimeUs, plan.clockBasis);
if (ptr != sendBuffer)
memcpy(sendBuffer, ptr, len);
sendLen = len;
const bool useBufferedIsr = shouldUseBufferedIsr();
txUseBufferedIsr_ = useBufferedIsr;
txActiveBufferedCtx_ = useBufferedIsr ? txBufferedCtx_ : nullptr;
if (!useBufferedIsr)
{
const TxFsmState initial = initialTxFsm(len);
storeTxFsmToMembers(initial);
{
const uint16_t cap = maxPowerNumerator();
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
}
legacyScaleAccumulator_ = 0U;
legacySlotInPeriod_ = 0;
isSending = true;
const uint32_t operationId = beginTxOperation(plan);
refreshBlindDecoderMuteState();
IR_Encoder::carrierResume();
markTxArmed(operationId);
return IR_SendResult(true, plan.airtimeMsCeil(), IR_SendStatus::Success,
operationId, plan.airtimeUs, plan.clockBasis);
}
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
if (buf == nullptr || !buf->isValid())
{
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
return IR_SendResult(false, 0U, IR_SendStatus::BufferedStorageInvalid,
0U, plan.airtimeUs, plan.clockBasis);
}
isSending = true;
const uint32_t operationId = beginTxOperation(plan);
refreshBlindDecoderMuteState();
buf->resetRuntimeState();
const IR_TxPlan built = buildPhysicalTransmission(
sendBuffer, len, buf->gateRuns, buf->maxGateRuns, plan.carrierMultiply);
if (!built.valid() || built.physicalTicks != plan.physicalTicks ||
built.gateRunCount != plan.gateRunCount)
{
const IR_SendStatus failure = built.valid() ? IR_SendStatus::PlanMismatch : built.status;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, failure);
return IR_SendResult(false, 0U, failure, operationId,
plan.airtimeUs, plan.clockBasis);
}
buf->totalTicks = plan.physicalTicks;
const uint32_t setW = (uint32_t)mask;
const uint32_t resetW = ((uint32_t)mask) << 16U;
{
const uint16_t cap = maxPowerNumerator();
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
}
buf->wave.configure(setW, resetW, buf->gateRuns,
static_cast<size_t>(built.gateRunCount),
plan.carrierMultiply, txPowerSnap_);
buf->wave.fill(buf->bsrrWords, buf->wordCount);
if (port != nullptr)
port->BSRR = resetW;
IR_Encoder::carrierResume();
markTxArmed(operationId);
return IR_SendResult(true, plan.airtimeMsCeil(), IR_SendStatus::Success,
operationId, plan.airtimeUs, plan.clockBasis);
}
void IR_Encoder::isr()
{
IR_Encoder *current = IR_Encoder::head;
while (current != nullptr)
{
current->_isr();
current = current->next;
}
}
void IR_Encoder::_isr()
{
if (!isSending)
return;
if (port == nullptr)
{
const uint32_t operationId = txOperationId_;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, IR_SendStatus::EncoderPinUnavailable);
carrierStopPending = true;
return;
}
if (!txUseBufferedIsr_)
{
const uint32_t setW = (uint32_t)mask;
const uint32_t resetW = ((uint32_t)mask) << 16U;
if (!state)
{
port->BSRR = resetW;
legacySlotInPeriod_ = 0;
}
else
{
port->BSRR = (legacySlotInPeriod_ < txPowerSnap_) ? setW : resetW;
legacySlotInPeriod_++;
if (legacySlotInPeriod_ >= txMultiplySnap_)
{
legacySlotInPeriod_ = 0;
}
}
legacyScaleAccumulator_ += 2U;
if (legacyScaleAccumulator_ < txMultiplySnap_)
{
return;
}
legacyScaleAccumulator_ -= txMultiplySnap_;
TxFsmState st{};
loadTxFsmFromMembers(st);
const bool active = txAdvanceAfterOutput(st, sendBuffer);
storeTxFsmToMembers(st);
if (!active)
{
const uint32_t operationId = txOperationId_;
port->BSRR = resetW;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, IR_SendStatus::Success);
carrierStopPending = true;
}
return;
}
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
if (buf == nullptr || !buf->isValid())
{
const uint32_t operationId = txOperationId_;
port->BSRR = ((uint32_t)mask) << 16U;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, IR_SendStatus::BufferedStorageInvalid);
carrierStopPending = true;
return;
}
port->BSRR = buf->bsrrWords[buf->readIdx];
buf->readIdx++;
buf->ticksSent++;
if (buf->ticksSent >= buf->totalTicks)
{
const uint32_t operationId = txOperationId_;
port->BSRR = ((uint32_t)mask) << 16U;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
finishTxOperation(operationId, IR_SendStatus::Success);
carrierStopPending = true;
return;
}
if (buf->readIdx == buf->halfLen)
{
buf->wave.fill(&buf->bsrrWords[0], buf->halfLen);
}
else if (buf->readIdx >= buf->wordCount)
{
buf->readIdx = 0;
buf->wave.fill(&buf->bsrrWords[buf->halfLen], buf->halfLen);
}
}
void IR_Encoder::sendByte(uint8_t byte, bool *prev, bool LOW_FIRST)
{
uint8_t mask = LOW_FIRST ? 0b00000001 : 0b10000000;
for (uint8_t bitShift = 8; bitShift; bitShift--)
{
// digitalWrite(9, HIGH);
// digitalWrite(9, LOW);
byte &mask ? send_HIGH(prev) : send_LOW();
*prev = byte & mask;
LOW_FIRST ? mask <<= 1 : mask >>= 1;
// digitalWrite(9, HIGH);
// digitalWrite(9, LOW);
}
}
void IR_Encoder::addSync(bool *prev, bool *next)
{
switch (syncBits)
{
case 0:
break;
case 1:
*prev ? send_LOW() : send_HIGH();
*prev = !*prev;
break;
default:
for (uint8_t i = 0; i < syncBits - 1U; i++)
{
*prev ? send_LOW() : send_HIGH();
*prev = !*prev;
}
*next ? send_LOW() : send_HIGH(0);
*prev = !*next;
break;
}
}
uint8_t IR_Encoder::bitHigh[2] = {
(bitPauseTakts) * 2 - 1,
(bitActiveTakts) * 2 - 1};
uint8_t IR_Encoder::bitLow[2] = {
(bitPauseTakts / 2 + bitActiveTakts) * 2 - 1,
(bitPauseTakts)-1};
uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
{
if (packSize == 0U || packSize > irproto::kMaxWireFrameBytes)
return 0U;
// Airtime is data-independent for the current PHY, but the source of
// truth remains the real FSM planner rather than a second size formula.
uint8_t frame[irproto::kMaxWireFrameBytes] = {0};
const IR_TxPlan plan = planTransmission(frame, packSize);
return plan.valid() ? plan.airtimeMsCeil() : 0U;
}
// Функции для тестирования времени отправки без фактической отправки
uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept) const
{
return testSendTime(addrTo, &dataByte, 1, needAccept);
}
uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
{
return testSendTimeFULL(id, addrTo, data, len, needAccept);
}
uint32_t IR_Encoder::testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
{
(void)addrFrom;
(void)addrTo;
(void)data;
(void)needAccept;
if (len > irproto::kMaxDataPayloadBytes)
{
return 0; // Возвращаем 0 для недопустимого размера
}
uint8_t packSize = msgBytes + addrBytes + addrBytes + len + crcBytes;
return calculateSendTime(packSize);
}
uint32_t IR_Encoder::testSendAccept(uint16_t addrTo, uint8_t customByte) const
{
(void)addrTo;
(void)customByte;
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
return calculateSendTime(packsize);
}
uint32_t IR_Encoder::testSendRequest(uint16_t addrTo) const
{
(void)addrTo;
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
return calculateSendTime(packsize);
}
uint32_t IR_Encoder::testSendBack(uint8_t data) const
{
return testSendBack(false, 0, &data, 1);
}
uint32_t IR_Encoder::testSendBack(uint8_t *data, uint8_t len) const
{
return testSendBack(false, 0, data, len);
}
uint32_t IR_Encoder::testSendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len) const
{
return testSendBack(true, addrTo, data, len);
}
uint32_t IR_Encoder::testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const
{
(void)addrTo;
(void)data;
const uint8_t payloadLimit = isAdressed ? irproto::kMaxBackToPayloadBytes
: irproto::kMaxBackPayloadBytes;
if (len > payloadLimit)
{
return 0; // Возвращаем 0 для недопустимого размера
}
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + len + crcBytes;
return calculateSendTime(packSize);
}
// uint8_t* IR_Encoder::bitHigh = new uint8_t[2]{
// (bitPauseTakts) * 2 - 0,
// (bitActiveTakts) * 2 - 0};
// uint8_t* IR_Encoder::bitLow = new uint8_t[2]{
// (bitPauseTakts/2 + bitActiveTakts) * 2 - 0,
// (bitPauseTakts) - 0};