Files
IR-protocol/IR_Encoder.cpp
DashyFox 8f45f6e214 feat(rx): наблюдаемость приёма по состоянию + airtime кадра формулами
- IR_config.h: irFrameAirtimeUs/irFrameDecodeEndUs/irLockToDecodeEndUs/irLockLatencyUs,
  irMaxPackSize (31), irDataPackSize/irBackPackSize — все из констант FSM передатчика
  (преамбула 6×98 тактов, байт 11 бит × 74 такта, такт = полпериода несущей).
- IR_Encoder::calculateSendTime по той же формуле (раньше синхробиты считались один раз
  на кадр → занижение 21-26%, потребители компенсировали +30%).
- IR_DecoderRaw: rxLockSeq/rxLockTimeUs (лок преамбулы), rxMsgType, rxExpectedEndUs
  (по объявленной длине), rxLastEnd {seq, reason Ok/Crc/Timeout/Abort, msgType, packSize,
  tUs, expectedEndUs}. Терминалы: конец кадра, таймаут тишины, немедленный abort при
  sync-ошибке / длине <3 (в т.ч. 0) / переполнении — битый кадр больше не держит
  isReciving до конца чужой передачи + 30 мс.
- Порядок в tick: checkTimeout до listenStart (TIMEOUT-лог с реальной длиной), истечение
  кандидата преамбулы без фронтов, ложный PREAMB-инкремент на старте кандидата убран.
- rxMaxPackSize() = протокольные 31 (было 38 = размер буфера).

Совместимость: только добавления; существующие сигнатуры не тронуты. Собрано для G4 (Car)
и F4 (КУ).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA1K5dBUKrnoXVwyoVjzq
2026-09-07 12:22:22 +03:00

1257 lines
35 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::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";
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 > irproto::kIsrTxMaxGateRuns)
{
return false;
}
IrTxGateRun copy[irproto::kIsrTxMaxGateRuns];
memcpy(copy, runs, nIn * sizeof(IrTxGateRun));
size_t w = 0;
for (size_t r = 0; r < nIn; r++)
{
uint32_t phys = (uint32_t)copy[r].lenTicks * (uint32_t)multiply / 2U;
if (copy[r].lenTicks > 0 && phys == 0)
{
phys = 1;
}
const bool g = copy[r].gate;
while (phys > 0)
{
if (w >= maxRuns)
{
return false;
}
const uint32_t chunk = phys > 65535U ? 65535U : phys;
runs[w].lenTicks = static_cast<uint16_t>(chunk);
runs[w].gate = g;
w++;
phys -= chunk;
}
}
*ioCount = w;
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);
}
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;
externalTxBusyFn = busyFn;
externalTxCtx = ctx;
}
void IR_Encoder::externalFinishSend()
{
if (!isSending)
return;
// Force output low.
if (port != nullptr) {
port->BSRR = ((uint32_t)mask) << 16;
}
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
}
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 > dataByteSizeMax)
{
return 0;
}
// Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
uint8_t sendBufferLocal[dataByteSizeMax] = {0};
memcpy(sendBufferLocal, packet, 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;
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 (packet == nullptr || outRuns == nullptr || maxRuns == 0)
{
return 0;
}
if (len == 0 || len > dataByteSizeMax)
{
return 0;
}
if (multiply < 2)
{
multiply = 2;
}
// Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
uint8_t sendBufferLocal[dataByteSizeMax] = {0};
memcpy(sendBufferLocal, packet, 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;
auto appendPhysicalRun = [&](bool gate, uint32_t logicalLen, size_t& runCount) -> bool {
if (logicalLen == 0)
{
return true;
}
uint32_t phys = (logicalLen * (uint32_t)multiply) / 2U;
if (logicalLen > 0 && phys == 0)
{
phys = 1;
}
while (phys > 0)
{
if (runCount >= maxRuns)
{
return false;
}
const uint32_t chunk = phys > 65535U ? 65535U : phys;
outRuns[runCount].gate = gate;
outRuns[runCount].lenTicks = static_cast<uint16_t>(chunk);
runCount++;
phys -= chunk;
}
return true;
};
size_t runCount = 0;
bool currentGate = false;
uint32_t currentLogicalLen = 0;
bool havePendingRun = false;
bool isActive = true;
while (isActive)
{
bool gate = false;
isActive = txEmitTick(st, sendBufferLocal, gate);
if (!havePendingRun)
{
currentGate = gate;
currentLogicalLen = 1U;
havePendingRun = true;
continue;
}
if (currentGate == gate)
{
currentLogicalLen++;
continue;
}
if (!appendPhysicalRun(currentGate, currentLogicalLen, runCount))
{
return 0;
}
currentGate = gate;
currentLogicalLen = 1U;
}
if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen, runCount))
{
return 0;
}
return runCount;
}
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 (((uint16_t)msgBytes + addrBytes + addrBytes + len + crcBytes) > IR_MASK_MSG_INFO)
{
Serial.println("IR Pack to big");
return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
}
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
memset(sendBuffer, 0x00, dataByteSizeMax);
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;
// }
// }
// отправка
const IR_SendStatus status = rawSend(sendBuffer, packSize);
if (status != IR_SendStatus::Success)
{
return IR_SendResult(false, 0, status);
}
// Возвращаем результат отправки
uint32_t sendTime = calculateSendTime(packSize);
return IR_SendResult(true, sendTime, status);
}
IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
{
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
memset(sendBuffer, 0x00, dataByteSizeMax);
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;
const IR_SendStatus status = rawSend(sendBuffer, packsize);
if (status != IR_SendStatus::Success)
{
return IR_SendResult(false, 0, status);
}
// Возвращаем результат отправки
uint32_t sendTime = calculateSendTime(packsize);
return IR_SendResult(true, sendTime, status);
}
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
{
constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
memset(sendBuffer, 0x00, dataByteSizeMax);
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;
const IR_SendStatus status = rawSend(sendBuffer, packsize);
if (status != IR_SendStatus::Success)
{
return IR_SendResult(false, 0, status);
}
// Возвращаем результат отправки
uint32_t sendTime = calculateSendTime(packsize);
return IR_SendResult(true, sendTime, status);
}
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.
if (((uint16_t)msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + len + crcBytes) > IR_MASK_MSG_INFO)
{
return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
}
memset(sendBuffer, 0x00, dataByteSizeMax);
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;
// отправка
const IR_SendStatus status = rawSend(sendBuffer, packSize);
if (status != IR_SendStatus::Success)
{
return IR_SendResult(false, 0, status);
}
// Возвращаем результат отправки
uint32_t sendTime = calculateSendTime(packSize);
return IR_SendResult(true, sendTime, status);
}
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)
{
if (isSending)
{
// TODO: Обработка повторной отправки
return IR_SendStatus::EncoderBusy;
}
// Проверка на переполнение буфера
if (len > dataByteSizeMax)
{
return IR_SendStatus::BufferTooLarge;
}
// Serial.print("IR tx hex: ");
// for (uint8_t i = 0; i < len; i++)
// {
// if (ptr[i] < 0x10) Serial.print("0");
// Serial.print(ptr[i], HEX);
// }
// Serial.println();
if (externalTxStartFn != nullptr)
{
if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
{
return IR_SendStatus::ExternalBackendBusy;
}
sendLen = len;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
isSending = true;
refreshBlindDecoderMuteState();
const IR_SendStatus status = externalTxStartFn(externalTxCtx, this, ptr, len);
if (status != IR_SendStatus::Success)
{
isSending = false;
refreshBlindDecoderMuteState();
}
return status;
}
if (port == nullptr || mask == 0)
{
return IR_SendStatus::EncoderPinUnavailable;
}
if (ptr != sendBuffer)
{
memcpy(sendBuffer, ptr, len);
}
sendLen = len;
const bool useBufferedIsr = shouldUseBufferedIsr();
txUseBufferedIsr_ = useBufferedIsr;
txActiveBufferedCtx_ = useBufferedIsr ? txBufferedCtx_ : nullptr;
if (!useBufferedIsr)
{
toggleCounter = preambToggle;
dataBitCounter = bitPerByte - 1;
dataByteCounter = 0;
preambFrontCounter = preambPulse * 2 - 1;
dataSequenceCounter = bitPerByte * 2;
syncSequenceCounter = syncBits * 2;
signal = preamb;
state = HIGH;
currentBitSequence = bitHigh;
txMultiplySnap_ = carrierMultiply();
{
const uint16_t cap = maxPowerNumerator();
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
}
legacyPhysPerLogical_ = static_cast<uint16_t>(txMultiplySnap_ / 2U);
if (legacyPhysPerLogical_ == 0)
{
legacyPhysPerLogical_ = 1;
}
legacyPhysCounter_ = 0;
legacySlotInPeriod_ = 0;
isSending = true;
refreshBlindDecoderMuteState();
IR_Encoder::carrierResume();
return IR_SendStatus::Success;
}
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
if (buf == nullptr || !buf->isValid())
{
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
return IR_SendStatus::BufferedStorageInvalid;
}
buf->resetRuntimeState();
txMultiplySnap_ = carrierMultiply();
size_t nRuns = buildPhysicalGateRuns(sendBuffer, len, buf->gateRuns, buf->maxGateRuns, txMultiplySnap_);
if (nRuns == 0U)
{
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
return IR_SendStatus::BuildGateRunsFailed;
}
uint32_t total = 0;
for (size_t i = 0; i < nRuns; i++)
{
total += buf->gateRuns[i].lenTicks;
}
buf->totalTicks = total;
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, nRuns, txMultiplySnap_, txPowerSnap_);
buf->wave.fill(buf->bsrrWords, buf->wordCount);
isSending = true;
refreshBlindDecoderMuteState();
if (port != nullptr)
{
port->BSRR = resetW;
}
IR_Encoder::carrierResume();
return IR_SendStatus::Success;
}
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)
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;
}
}
legacyPhysCounter_++;
if (legacyPhysCounter_ < legacyPhysPerLogical_)
{
return;
}
legacyPhysCounter_ = 0;
TxFsmState st{};
loadTxFsmFromMembers(st);
const bool active = txAdvanceAfterOutput(st, sendBuffer);
storeTxFsmToMembers(st);
if (!active)
{
port->BSRR = resetW;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
carrierStopPending = true;
}
return;
}
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
if (buf == nullptr || !buf->isValid())
{
port->BSRR = ((uint32_t)mask) << 16U;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
carrierStopPending = true;
return;
}
port->BSRR = buf->bsrrWords[buf->readIdx];
buf->readIdx++;
buf->ticksSent++;
if (buf->ticksSent >= buf->totalTicks)
{
port->BSRR = ((uint32_t)mask) << 16U;
isSending = false;
txUseBufferedIsr_ = false;
txActiveBufferedCtx_ = nullptr;
refreshBlindDecoderMuteState();
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 (int16_t i = 0; i < syncBits - 1; 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
{
// Полное время кадра в эфире по формуле FSM (преамбула + байты с синхробитами), округление вверх до мс.
return (irFrameAirtimeUs(packSize) + 999U) / 1000U;
}
// Функции для тестирования времени отправки без фактической отправки
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
{
if (len > bytePerPack)
{
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
{
constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
return calculateSendTime(packsize);
}
uint32_t IR_Encoder::testSendRequest(uint16_t addrTo) const
{
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
{
if (((uint16_t)msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + len + crcBytes) > IR_MASK_MSG_INFO)
{
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};