mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-21 12:29:35 +00:00
1537 lines
46 KiB
C++
1537 lines
46 KiB
C++
#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};
|