#include "IR_Encoder.h" #include "IR_DecoderRaw.h" #include "IrTxIsrBufferedStorage.h" #include #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(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(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(multiply) + 1U) / 2U; const uint64_t physicalLen = nextPhysicalBoundary - physicalBoundary; physicalBoundary = nextPhysicalBoundary; const uint64_t chunks = (physicalLen + 65534U) / 65535U; if (chunks > static_cast(maxRuns - outputCount)) { return false; } outputCount += static_cast(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(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(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(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(carrierFrec) * static_cast(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(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( physicalLen > 65535U ? 65535U : physicalLen); if (emitRuns && static_cast(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(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(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(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(IR_Timer->getPrescaleFactor()) * static_cast(IR_Timer->getOverflow(TICK_FORMAT)); if (timerClockHz == 0U || divider == 0U || divider > UINT32_MAX) { plan.status = IR_SendStatus::TimingOverflow; return; } applyTickClock(plan, timerClockHz, static_cast(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(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};