diff --git a/components/espsoftwareserial/SoftwareSerial.cpp b/components/espsoftwareserial/SoftwareSerial.cpp new file mode 100644 index 0000000..f058954 --- /dev/null +++ b/components/espsoftwareserial/SoftwareSerial.cpp @@ -0,0 +1,619 @@ +/* + +SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "SoftwareSerial.h" +#include + +using namespace EspSoftwareSerial; + +#ifndef ESP32 +uint32_t UARTBase::m_savedPS = 0; +#else +portMUX_TYPE UARTBase::m_interruptsMux = portMUX_INITIALIZER_UNLOCKED; +#endif + +__attribute__((always_inline)) inline void IRAM_ATTR UARTBase::disableInterrupts() +{ +#ifndef ESP32 + m_savedPS = xt_rsil(15); +#else + taskENTER_CRITICAL(&m_interruptsMux); +#endif +} + +__attribute__((always_inline)) inline void IRAM_ATTR UARTBase::restoreInterrupts() +{ +#ifndef ESP32 + xt_wsr_ps(m_savedPS); +#else + taskEXIT_CRITICAL(&m_interruptsMux); +#endif +} + +constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast(0); + +UARTBase::UARTBase() { +} + +UARTBase::UARTBase(int8_t rxPin, int8_t txPin, bool invert) +{ + m_rxPin = rxPin; + m_txPin = txPin; + m_invert = invert; +} + +UARTBase::~UARTBase() { + end(); +} + +void UARTBase::setRxGPIOPinMode() { + if (m_rxValid) { + pinMode(m_rxPin, m_rxGPIOHasPullUp && m_rxGPIOPullUpEnabled ? INPUT_PULLUP : INPUT); + } +} + +void UARTBase::setTxGPIOPinMode() { + if (m_txValid) { + pinMode(m_txPin, m_txGPIOOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); + } +} + +void UARTBase::begin(uint32_t baud, Config config, + int8_t rxPin, int8_t txPin, + bool invert) { + if (-1 != rxPin) m_rxPin = rxPin; + if (-1 != txPin) m_txPin = txPin; + m_oneWire = (m_rxPin == m_txPin); + m_invert = invert; + m_dataBits = 5 + (config & 07); + m_parityMode = static_cast(config & 070); + m_stopBits = 1 + ((config & 0300) ? 1 : 0); + m_pduBits = m_dataBits + static_cast(m_parityMode) + m_stopBits; + m_bitTicks = (microsToTicks(1000000UL) + baud / 2) / baud; + m_intTxEnabled = true; +} + +void UARTBase::beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity) { + m_rxGPIOHasPullUp = hasPullUp; + m_rxReg = portInputRegister(digitalPinToPort(m_rxPin)); + m_rxBitMask = digitalPinToBitMask(m_rxPin); + m_buffer.reset(new circular_queue((bufCapacity > 0) ? bufCapacity : 64)); + if (m_parityMode) + { + m_parityBuffer.reset(new circular_queue((m_buffer->capacity() + 7) / 8)); + m_parityInPos = m_parityOutPos = 1; + } + m_isrBuffer.reset(new circular_queue((isrBufCapacity > 0) ? + isrBufCapacity : m_buffer->capacity() * (2 + m_dataBits + static_cast(m_parityMode)))); + if (m_buffer && (!m_parityMode || m_parityBuffer) && m_isrBuffer) { + m_rxValid = true; + setRxGPIOPinMode(); + } +} + +void UARTBase::beginTx() { +#if !defined(ESP8266) + m_txReg = portOutputRegister(digitalPinToPort(m_txPin)); +#endif + m_txBitMask = digitalPinToBitMask(m_txPin); + m_txValid = true; + if (!m_oneWire) { + setTxGPIOPinMode(); + digitalWrite(m_txPin, !m_invert); + } +} + +void UARTBase::end() +{ + enableRx(false); + m_txValid = false; + if (m_buffer) { + m_buffer.reset(); + } + m_parityBuffer.reset(); + if (m_isrBuffer) { + m_isrBuffer.reset(); + } +} + +uint32_t UARTBase::baudRate() { + return 1000000UL / ticksToMicros(m_bitTicks); +} + +void UARTBase::setTransmitEnablePin(int8_t txEnablePin) { + if (-1 != txEnablePin) { + m_txEnableValid = true; + m_txEnablePin = txEnablePin; + pinMode(m_txEnablePin, OUTPUT); + digitalWrite(m_txEnablePin, LOW); + } + else { + m_txEnableValid = false; + } +} + +void UARTBase::enableIntTx(bool on) { + m_intTxEnabled = on; +} + +void UARTBase::enableRxGPIOPullUp(bool on) { + m_rxGPIOPullUpEnabled = on; + setRxGPIOPinMode(); +} + +void UARTBase::enableTxGPIOOpenDrain(bool on) { + m_txGPIOOpenDrain = on; + setTxGPIOPinMode(); +} + +void UARTBase::enableTx(bool on) { + if (m_txValid && m_oneWire) { + if (on) { + enableRx(false); + setTxGPIOPinMode(); + digitalWrite(m_txPin, !m_invert); + } + else { + setRxGPIOPinMode(); + enableRx(true); + } + } +} + +void UARTBase::enableRx(bool on) { + if (m_rxValid && on != m_rxEnabled) { + if (on) { + m_rxLastBit = m_pduBits - 1; + // Init to stop bit level and current tick + m_isrLastTick = (microsToTicks(micros()) | 1) ^ m_invert; + if (m_bitTicks >= microsToTicks(1000000UL / 74880UL)) + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitISR), this, CHANGE); + else + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitSyncISR), this, m_invert ? RISING : FALLING); + } + else { + detachInterrupt(digitalPinToInterrupt(m_rxPin)); + } + m_rxEnabled = on; + } +} + +int UARTBase::read() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) { return -1; } + } + auto val = m_buffer->pop(); + if (m_parityBuffer) + { + m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos; + m_parityOutPos <<= 1; + if (!m_parityOutPos) + { + m_parityOutPos = 1; + m_parityBuffer->pop(); + } + } + return val; +} + +int UARTBase::read(uint8_t* buffer, size_t size) { + if (!m_rxValid) { return 0; } + int avail; + if (0 == (avail = m_buffer->pop_n(buffer, size))) { + rxBits(); + avail = m_buffer->pop_n(buffer, size); + } + if (!avail) return 0; + if (m_parityBuffer) { + uint32_t parityBits = avail; + while (m_parityOutPos >>= 1) ++parityBits; + m_parityOutPos = (1 << (parityBits % 8)); + m_parityBuffer->pop_n(nullptr, parityBits / 8); + } + return avail; +} + +size_t UARTBase::readBytes(uint8_t* buffer, size_t size) { + if (!m_rxValid || !size) { return 0; } + size_t count = 0; + auto start = millis(); + do { + auto readCnt = read(&buffer[count], size - count); + count += readCnt; + if (count >= size) break; + if (readCnt) { + start = millis(); + } + else { + optimistic_yield(1000UL); + } + } while (millis() - start < _timeout); + return count; +} + +int UARTBase::available() { + if (!m_rxValid) { return 0; } + rxBits(); + int avail = m_buffer->available(); + if (!avail) { + optimistic_yield(10000UL); + } + return avail; +} + +void UARTBase::lazyDelay() { + // Reenable interrupts while delaying to avoid other tasks piling up + if (!m_intTxEnabled) { restoreInterrupts(); } + const auto expired = microsToTicks(micros()) - m_periodStart; + const int32_t remaining = m_periodDuration - expired; + const uint32_t ms = remaining > 0 ? ticksToMicros(remaining) / 1000UL : 0; + if (ms > 0) + { + delay(ms); + } + else + { + optimistic_yield(10000UL); + } + // Assure that below-ms part of delays are not elided + preciseDelay(); + // Disable interrupts again if applicable + if (!m_intTxEnabled) { disableInterrupts(); } +} + +void IRAM_ATTR UARTBase::preciseDelay() { + uint32_t ticks; + do { + ticks = microsToTicks(micros()); + } while ((ticks - m_periodStart) < m_periodDuration); + m_periodDuration = 0; + m_periodStart = ticks; +} + +void IRAM_ATTR UARTBase::writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) { + preciseDelay(); + if (dutyCycle) + { +#if defined(ESP8266) + if (16 == m_txPin) { + GP16O = 1; + } + else { + GPOS = m_txBitMask; + } +#else + *m_txReg |= m_txBitMask; +#endif + m_periodDuration += dutyCycle; + if (offCycle || (withStopBit && !m_invert)) { + if (!withStopBit || m_invert) { + preciseDelay(); + } + else { + lazyDelay(); + } + } + } + if (offCycle) + { +#if defined(ESP8266) + if (16 == m_txPin) { + GP16O = 0; + } + else { + GPOC = m_txBitMask; + } +#else + *m_txReg &= ~m_txBitMask; +#endif + m_periodDuration += offCycle; + if (withStopBit && m_invert) lazyDelay(); + } +} + +size_t UARTBase::write(uint8_t byte) { + return write(&byte, 1); +} + +size_t UARTBase::write(uint8_t byte, Parity parity) { + return write(&byte, 1, parity); +} + +size_t UARTBase::write(const uint8_t* buffer, size_t size) { + return write(buffer, size, m_parityMode); +} + +size_t IRAM_ATTR UARTBase::write(const uint8_t* buffer, size_t size, Parity parity) { + if (m_rxValid) { rxBits(); } + if (!m_txValid) { return -1; } + + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, HIGH); + } + // Stop bit: if inverted, LOW, otherwise HIGH + bool b = !m_invert; + uint32_t dutyCycle = 0; + uint32_t offCycle = 0; + if (!m_intTxEnabled) { + // Disable interrupts in order to get a clean transmit timing + disableInterrupts(); + } + const uint32_t dataMask = ((1UL << m_dataBits) - 1); + bool withStopBit = true; + m_periodDuration = 0; + m_periodStart = microsToTicks(micros()); + for (size_t cnt = 0; cnt < size; ++cnt) { + uint8_t byte = pgm_read_byte(buffer + cnt) & dataMask; + // push LSB start-data-parity-stop bit pattern into uint32_t + // Stop bits: HIGH + uint32_t word = ~0UL; + // inverted parity bit, performance tweak for xor all-bits-set word + if (parity && m_parityMode) + { + uint32_t parityBit; + switch (parity) + { + case PARITY_EVEN: + // from inverted, so use odd parity + parityBit = byte; + parityBit ^= parityBit >> 4; + parityBit &= 0xf; + parityBit = (0x9669 >> parityBit) & 1; + break; + case PARITY_ODD: + // from inverted, so use even parity + parityBit = byte; + parityBit ^= parityBit >> 4; + parityBit &= 0xf; + parityBit = (0x6996 >> parityBit) & 1; + break; + case PARITY_MARK: + parityBit = 0; + break; + case PARITY_SPACE: + // suppresses warning parityBit uninitialized + default: + parityBit = 1; + break; + } + word ^= parityBit; + } + word <<= m_dataBits; + word |= byte; + // Start bit: LOW + word <<= 1; + if (m_invert) word = ~word; + for (int i = 0; i <= m_pduBits; ++i) { + bool pb = b; + b = word & (1UL << i); + if (!pb && b) { + writePeriod(dutyCycle, offCycle, withStopBit); + withStopBit = false; + dutyCycle = offCycle = 0; + } + if (b) { + dutyCycle += m_bitTicks; + } + else { + offCycle += m_bitTicks; + } + } + withStopBit = true; + } + writePeriod(dutyCycle, offCycle, true); + if (!m_intTxEnabled) { + // restore the interrupt state if applicable + restoreInterrupts(); + } + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, LOW); + } + return size; +} + +void UARTBase::flush() { + if (!m_rxValid) { return; } + m_buffer->flush(); + if (m_parityBuffer) + { + m_parityInPos = m_parityOutPos = 1; + m_parityBuffer->flush(); + } +} + +bool UARTBase::overflow() { + bool res = m_overflow; + m_overflow = false; + return res; +} + +int UARTBase::peek() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) return -1; + } + auto val = m_buffer->peek(); + if (m_parityBuffer) m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos; + return val; +} + +void UARTBase::rxBits() { +#ifdef ESP8266 + if (m_isrOverflow.load()) { + m_overflow = true; + m_isrOverflow.store(false); + } +#else + if (m_isrOverflow.exchange(false)) { + m_overflow = true; + } +#endif + + m_isrBuffer->for_each(m_isrBufferForEachDel); + + // A stop bit can go undetected if leading data bits are at same level + // and there was also no next start bit yet, so one word may be pending. + // Check that there was no new ISR data received in the meantime, inserting an + // extraneous stop level bit out of sequence breaks rx. + if (m_rxLastBit < m_pduBits - 1) { + const uint32_t detectionTicks = (m_pduBits - 1 - m_rxLastBit) * m_bitTicks; + if (!m_isrBuffer->available() && microsToTicks(micros()) - m_isrLastTick > detectionTicks) { + // Produce faux stop bit level, prevents start bit maldetection + // tick's LSB is repurposed for the level bit + rxBits(((m_isrLastTick + detectionTicks) | 1) ^ m_invert); + } + } +} + +void UARTBase::rxBits(const uint32_t isrTick) { + const bool level = (m_isrLastTick & 1) ^ m_invert; + + // error introduced by edge value in LSB of isrTick is negligible + uint32_t ticks = isrTick - m_isrLastTick; + m_isrLastTick = isrTick; + + uint32_t bits = ticks / m_bitTicks; + if (ticks % m_bitTicks > (m_bitTicks >> 1)) ++bits; + while (bits > 0) { + // start bit detection + if (m_rxLastBit >= (m_pduBits - 1)) { + // leading edge of start bit? + if (level) break; + m_rxLastBit = -1; + --bits; + continue; + } + // data bits + if (m_rxLastBit < (m_dataBits - 1)) { + uint8_t dataBits = min(bits, static_cast(m_dataBits - 1 - m_rxLastBit)); + m_rxLastBit += dataBits; + bits -= dataBits; + m_rxCurByte >>= dataBits; + if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); } + continue; + } + // parity bit + if (m_parityMode && m_rxLastBit == (m_dataBits - 1)) { + ++m_rxLastBit; + --bits; + m_rxCurParity = level; + continue; + } + // stop bits + // Store the received value in the buffer unless we have an overflow + // if not high stop bit level, discard word + if (bits >= static_cast(m_pduBits - 1 - m_rxLastBit) && level) { + m_rxCurByte >>= (sizeof(uint8_t) * 8 - m_dataBits); + if (!m_buffer->push(m_rxCurByte)) { + m_overflow = true; + } + else { + if (m_parityBuffer) + { + if (m_rxCurParity) { + m_parityBuffer->pushpeek() |= m_parityInPos; + } + else { + m_parityBuffer->pushpeek() &= ~m_parityInPos; + } + m_parityInPos <<= 1; + if (!m_parityInPos) + { + m_parityBuffer->push(); + m_parityInPos = 1; + } + } + } + } + m_rxLastBit = m_pduBits - 1; + // reset to 0 is important for masked bit logic + m_rxCurByte = 0; + m_rxCurParity = false; + break; + } +} + +void IRAM_ATTR UARTBase::rxBitISR(UARTBase* self) { + const bool level = *self->m_rxReg & self->m_rxBitMask; + const uint32_t curTick = microsToTicks(micros()); + const bool empty = !self->m_isrBuffer->available(); + + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push((curTick | 1U) ^ !level)) self->m_isrOverflow.store(true); + // Trigger rx callback only when receiver is starved + if (empty) self->m_rxHandler(); +} + +void IRAM_ATTR UARTBase::rxBitSyncISR(UARTBase* self) { + bool level = self->m_invert; + const uint32_t start = microsToTicks(micros()); + uint32_t wait = self->m_bitTicks; + const bool empty = !self->m_isrBuffer->available(); + + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true); + + for (uint32_t i = 0; i < self->m_pduBits; ++i) { + while (microsToTicks(micros()) - start < wait) {}; + wait += self->m_bitTicks; + + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (static_cast(*self->m_rxReg & self->m_rxBitMask) != level) + { + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true); + level = !level; + } + } + // Trigger rx callback only when receiver is starved + if (empty) self->m_rxHandler(); +} + +void UARTBase::onReceive(const Delegate& handler) { + disableInterrupts(); + m_rxHandler = handler; + restoreInterrupts(); +} + +void UARTBase::onReceive(Delegate&& handler) { + disableInterrupts(); + m_rxHandler = std::move(handler); + restoreInterrupts(); +} + +// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently +// honor the attribute. Instead, it is possible to do explicit specialization and adorn +// these with the IRAM attribute: +// Delegate<>::operator (), circular_queue<>::available, +// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push + +template void IRAM_ATTR delegate::detail::DelegateImpl::operator()() const; +template size_t IRAM_ATTR circular_queue::available() const; +template bool IRAM_ATTR circular_queue::push(uint32_t&&); +template bool IRAM_ATTR circular_queue::push(const uint32_t&); + diff --git a/components/espsoftwareserial/SoftwareSerial.h b/components/espsoftwareserial/SoftwareSerial.h new file mode 100644 index 0000000..76437a8 --- /dev/null +++ b/components/espsoftwareserial/SoftwareSerial.h @@ -0,0 +1,447 @@ +/* +SoftwareSerial.h - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef __SoftwareSerial_h +#define __SoftwareSerial_h + +#include "circular_queue/circular_queue.h" +#include + +namespace EspSoftwareSerial { + +// Interface definition for template argument of BasicUART +class IGpioCapabilities { +public: + static constexpr bool isValidPin(int8_t pin); + static constexpr bool isValidInputPin(int8_t pin); + static constexpr bool isValidOutputPin(int8_t pin); + // result is only defined for a valid Rx pin + static constexpr bool hasPullUp(int8_t pin); +}; + +class GpioCapabilities : private IGpioCapabilities { +public: + static constexpr bool isValidPin(int8_t pin) { + #if defined(ESP8266) + return (pin >= 0 && pin <= 16) && !isFlashInterfacePin(pin); + #elif defined(ESP32) + // Remove the strapping pins as defined in the datasheets, they affect bootup and other critical operations + // Remmove the flash memory pins on related devices, since using these causes memory access issues. + #ifdef CONFIG_IDF_TARGET_ESP32 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/esp32-devkitC-v4-pinout.jpg + return (pin == 1) || (pin >= 3 && pin <= 5) || + (pin >= 12 && pin <= 15) || + (!psramFound() && pin >= 16 && pin <= 17) || + (pin >= 18 && pin <= 19) || + (pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 39); + #elif CONFIG_IDF_TARGET_ESP32S2 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg + return (pin >= 1 && pin <= 21) || (pin >= 33 && pin <= 44); + #elif CONFIG_IDF_TARGET_ESP32C3 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/_images/esp32-c3-devkitm-1-v1-pinout.jpg + return (pin >= 0 && pin <= 1) || (pin >= 3 && pin <= 7) || (pin >= 18 && pin <= 21); + #else + return pin >= 0; + #endif + #else + return pin >= 0; + #endif + } + + static constexpr bool isValidInputPin(int8_t pin) { + return isValidPin(pin) + #if defined(ESP8266) + && (pin != 16) + #endif + ; + } + + static constexpr bool isValidOutputPin(int8_t pin) { + return isValidPin(pin) + #if defined(ESP32) + #ifdef CONFIG_IDF_TARGET_ESP32 + && (pin < 34) + #elif CONFIG_IDF_TARGET_ESP32S2 + && (pin <= 45) + #elif CONFIG_IDF_TARGET_ESP32C3 + // no restrictions + #endif + #endif + ; + } + + // result is only defined for a valid Rx pin + static constexpr bool hasPullUp(int8_t pin) { + #if defined(ESP32) + return !(pin >= 34 && pin <= 39); + #else + (void)pin; + return true; + #endif + } +}; + +enum Parity : uint8_t { + PARITY_NONE = 000, + PARITY_EVEN = 020, + PARITY_ODD = 030, + PARITY_MARK = 040, + PARITY_SPACE = 070, +}; + +enum Config { + SWSERIAL_5N1 = PARITY_NONE, + SWSERIAL_6N1, + SWSERIAL_7N1, + SWSERIAL_8N1, + SWSERIAL_5E1 = PARITY_EVEN, + SWSERIAL_6E1, + SWSERIAL_7E1, + SWSERIAL_8E1, + SWSERIAL_5O1 = PARITY_ODD, + SWSERIAL_6O1, + SWSERIAL_7O1, + SWSERIAL_8O1, + SWSERIAL_5M1 = PARITY_MARK, + SWSERIAL_6M1, + SWSERIAL_7M1, + SWSERIAL_8M1, + SWSERIAL_5S1 = PARITY_SPACE, + SWSERIAL_6S1, + SWSERIAL_7S1, + SWSERIAL_8S1, + SWSERIAL_5N2 = 0200 | PARITY_NONE, + SWSERIAL_6N2, + SWSERIAL_7N2, + SWSERIAL_8N2, + SWSERIAL_5E2 = 0200 | PARITY_EVEN, + SWSERIAL_6E2, + SWSERIAL_7E2, + SWSERIAL_8E2, + SWSERIAL_5O2 = 0200 | PARITY_ODD, + SWSERIAL_6O2, + SWSERIAL_7O2, + SWSERIAL_8O2, + SWSERIAL_5M2 = 0200 | PARITY_MARK, + SWSERIAL_6M2, + SWSERIAL_7M2, + SWSERIAL_8M2, + SWSERIAL_5S2 = 0200 | PARITY_SPACE, + SWSERIAL_6S2, + SWSERIAL_7S2, + SWSERIAL_8S2, +}; + +/// This class is compatible with the corresponding AVR one, however, +/// the constructor takes no arguments, for compatibility with the +/// HardwareSerial class. +/// Instead, the begin() function handles pin assignments and logic inversion. +/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer. +/// Bitrates up to at least 115200 can be used. +class UARTBase : public Stream { +public: + UARTBase(); + /// Ctor to set defaults for pins. + /// @param rxPin the GPIO pin used for RX + /// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX + UARTBase(int8_t rxPin, int8_t txPin = -1, bool invert = false); + UARTBase(const UARTBase&) = delete; + UARTBase& operator= (const UARTBase&) = delete; + virtual ~UARTBase(); + /// Configure the UARTBase object for use. + /// @param baud the TX/RX bitrate + /// @param config sets databits, parity, and stop bit count + /// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor + /// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor + /// @param invert true: uses invert line level logic + /// @param bufCapacity the capacity for the received bytes buffer + /// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous + /// bit receive buffer, a suggested size is bufCapacity times the sum of + /// start, data, parity and stop bit count. + void begin(uint32_t baud, Config config, + int8_t rxPin, int8_t txPin, bool invert); + + uint32_t baudRate(); + /// Transmit control pin. + void setTransmitEnablePin(int8_t txEnablePin); + /// Enable (default) or disable interrupts during tx. + void enableIntTx(bool on); + /// Enable (default) or disable internal rx GPIO pull-up. + void enableRxGPIOPullUp(bool on); + /// Enable or disable (default) tx GPIO output mode. + void enableTxGPIOOpenDrain(bool on); + + bool overflow(); + + int available() override; +#if defined(ESP8266) + int availableForWrite() override { +#else + int availableForWrite() { +#endif + if (!m_txValid) return 0; + return 1; + } + int peek() override; + int read() override; + /// @returns The verbatim parity bit associated with the last successful read() or peek() call + bool readParity() + { + return m_lastReadParity; + } + /// @returns The calculated bit for even parity of the parameter byte + static bool parityEven(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; + } + /// @returns The calculated bit for odd parity of the parameter byte + static bool parityOdd(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x9669 >> byte) & 1; + } + /// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout + int read(uint8_t* buffer, size_t size) +#if defined(ESP8266) + override +#endif + ; + /// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout + int read(char* buffer, size_t size) { + return read(reinterpret_cast(buffer), size); + } + /// @returns The number of bytes read into buffer, up to size. Times out if the limit set through + /// Stream::setTimeout() is reached. + size_t readBytes(uint8_t* buffer, size_t size) override; + /// @returns The number of bytes read into buffer, up to size. Times out if the limit set through + /// Stream::setTimeout() is reached. + size_t readBytes(char* buffer, size_t size) override { + return readBytes(reinterpret_cast(buffer), size); + } + void flush() override; + size_t write(uint8_t byte) override; + size_t write(uint8_t byte, Parity parity); + size_t write(const uint8_t* buffer, size_t size) override; + size_t write(const char* buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } + size_t write(const uint8_t* buffer, size_t size, Parity parity); + size_t write(const char* buffer, size_t size, Parity parity) { + return write(reinterpret_cast(buffer), size, parity); + } + operator bool() const { + return (-1 == m_rxPin || m_rxValid) && (-1 == m_txPin || m_txValid) && !(-1 == m_rxPin && m_oneWire); + } + + /// Disable or enable interrupts on the rx pin. + void enableRx(bool on); + /// One wire control. + void enableTx(bool on); + + // AVR compatibility methods. + bool listen() { enableRx(true); return true; } + void end(); + bool isListening() { return m_rxEnabled; } + bool stopListening() { enableRx(false); return true; } + + /// onReceive sets a callback that will be called in interrupt context + /// when data is received. + /// More precisely, the callback is triggered when UARTBase detects + /// a new reception, which may not yet have completed on invocation. + /// Reading - never from this interrupt context - should therefore be + /// delayed at least for the duration of one incoming word. + void onReceive(const Delegate& handler); + /// onReceive sets a callback that will be called in interrupt context + /// when data is received. + /// More precisely, the callback is triggered when UARTBase detects + /// a new reception, which may not yet have completed on invocation. + /// Reading - never from this interrupt context - should therefore be + /// delayed at least for the duration of one incoming word. + void onReceive(Delegate&& handler); + + [[deprecated("function removed; semantics of onReceive() changed; check the header file.")]] + void perform_work(); + + using Print::write; + +protected: + void beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity); + void beginTx(); + // Member variables + int8_t m_rxPin = -1; + int8_t m_txPin = -1; + bool m_invert = false; + +private: + // It's legal to exceed the deadline, for instance, + // by enabling interrupts. + void lazyDelay(); + // Synchronous precise delay + void preciseDelay(); + // If withStopBit is set, either cycle contains a stop bit. + // If dutyCycle == 0, the level is not forced to HIGH. + // If offCycle == 0, the level remains unchanged from dutyCycle. + void writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit); + // safely set the pin mode for the Rx GPIO pin + void setRxGPIOPinMode(); + // safely set the pin mode for the Tx GPIO pin + void setTxGPIOPinMode(); + /* check m_rxValid that calling is safe */ + void rxBits(); + void rxBits(const uint32_t isrTick); + static void disableInterrupts(); + static void restoreInterrupts(); + + static void rxBitISR(UARTBase* self); + static void rxBitSyncISR(UARTBase* self); + + static inline uint32_t IRAM_ATTR microsToTicks(uint32_t micros) __attribute__((always_inline)) { + return micros << 1; + } + static inline uint32_t ticksToMicros(uint32_t ticks) __attribute__((always_inline)) { + return ticks >> 1; + } + + // Member variables + volatile uint32_t* m_rxReg; + uint32_t m_rxBitMask; +#if !defined(ESP8266) + volatile uint32_t* m_txReg; +#endif + uint32_t m_txBitMask; + int8_t m_txEnablePin = -1; + uint8_t m_dataBits; + bool m_oneWire; + bool m_rxValid = false; + bool m_rxEnabled = false; + bool m_txValid = false; + bool m_txEnableValid = false; + /// PDU bits include data, parity and stop bits; the start bit is not counted. + uint8_t m_pduBits; + bool m_intTxEnabled; + bool m_rxGPIOHasPullUp = false; + bool m_rxGPIOPullUpEnabled = true; + bool m_txGPIOOpenDrain = false; + Parity m_parityMode; + uint8_t m_stopBits; + bool m_lastReadParity; + bool m_overflow = false; + uint32_t m_bitTicks; + uint8_t m_parityInPos; + uint8_t m_parityOutPos; + int8_t m_rxLastBit; // 0 thru (m_pduBits - m_stopBits - 1): data/parity bits. -1: start bit. (m_pduBits - 1): stop bit. + uint8_t m_rxCurByte = 0; + std::unique_ptr > m_buffer; + std::unique_ptr > m_parityBuffer; + uint32_t m_periodStart; + uint32_t m_periodDuration; +#ifndef ESP32 + static uint32_t m_savedPS; +#else + static portMUX_TYPE m_interruptsMux; +#endif + // the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement): + // 1 = positive including 0, 0 = negative. + std::unique_ptr > m_isrBuffer; + const Delegate m_isrBufferForEachDel { [](UARTBase* self, uint32_t&& isrTick) { self->rxBits(isrTick); }, this }; + std::atomic m_isrOverflow { false }; + uint32_t m_isrLastTick; + bool m_rxCurParity = false; + Delegate m_rxHandler; +}; + +template< class GpioCapabilities > class BasicUART : public UARTBase { + static_assert(std::is_base_of::value, + "template argument is not derived from IGpioCapabilities"); +public: + BasicUART() : UARTBase() { + } + /// Ctor to set defaults for pins. + /// @param rxPin the GPIO pin used for RX + /// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX + BasicUART(int8_t rxPin, int8_t txPin = -1, bool invert = false) : + UARTBase(rxPin, txPin, invert) { + } + + /// Configure the BasicUART object for use. + /// @param baud the TX/RX bitrate + /// @param config sets databits, parity, and stop bit count + /// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor + /// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor + /// @param invert true: uses invert line level logic + /// @param bufCapacity the capacity for the received bytes buffer + /// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous + /// bit receive buffer, a suggested size is bufCapacity times the sum of + /// start, data, parity and stop bit count. + void begin(uint32_t baud, Config config, + int8_t rxPin, int8_t txPin, bool invert, + int bufCapacity = 64, int isrBufCapacity = 0) { + UARTBase::begin(baud, config, rxPin, txPin, invert); + if (GpioCapabilities::isValidInputPin(rxPin)) { + beginRx(GpioCapabilities:: hasPullUp(rxPin), bufCapacity, isrBufCapacity); + } + if (GpioCapabilities::isValidOutputPin(txPin)) { + beginTx(); + } + enableRx(true); + } + void begin(uint32_t baud, Config config, + int8_t rxPin, int8_t txPin) { + begin(baud, config, rxPin, txPin, m_invert); + } + void begin(uint32_t baud, Config config, + int8_t rxPin) { + begin(baud, config, rxPin, m_txPin, m_invert); + } + void begin(uint32_t baud, Config config = SWSERIAL_8N1) { + begin(baud, config, m_rxPin, m_txPin, m_invert); + } + void setTransmitEnablePin(int8_t txEnablePin) { + UARTBase::setTransmitEnablePin( + GpioCapabilities::isValidOutputPin(txEnablePin) ? txEnablePin : -1); + } +}; + +using UART = BasicUART< GpioCapabilities >; + +}; // namespace EspSoftwareSerial + +using SoftwareSerial = EspSoftwareSerial::UART; +using namespace EspSoftwareSerial; + +// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently +// honor the attribute. Instead, it is possible to do explicit specialization and adorn +// these with the IRAM attribute: +// Delegate<>::operator (), circular_queue<>::available, +// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push + +extern template void delegate::detail::DelegateImpl::operator()() const; +extern template size_t circular_queue::available() const; +extern template bool circular_queue::push(uint32_t&&); +extern template bool circular_queue::push(const uint32_t&); + +#endif // __SoftwareSerial_h + diff --git a/components/espsoftwareserial/circular_queue/Delegate.h b/components/espsoftwareserial/circular_queue/Delegate.h new file mode 100644 index 0000000..461287e --- /dev/null +++ b/components/espsoftwareserial/circular_queue/Delegate.h @@ -0,0 +1,2202 @@ +/* +Delegate.h - An efficient interchangeable C function ptr and C++ std::function delegate +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __Delegate_h +#define __Delegate_h + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#else +#define IRAM_ATTR +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) +#include +#include +#else +#include "circular_queue/ghostl.h" +#endif + +namespace +{ + + template + __attribute__((always_inline)) inline R IRAM_ATTR vPtrToFunPtrExec(void* fn, P... args) + { + using target_type = R(P...); + return reinterpret_cast(fn)(std::forward(args...)); + } + +} + +namespace delegate +{ + namespace detail + { + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + using FunctionType = std::function; + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) __attribute__((always_inline)) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this](P... args) { return fnA(obj, std::forward(args...)); }; + } + else + { + return functional; + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + if (fn) return fn(std::forward(args...)); + } + else if (FPA == kind) + { + if (fnA) return fnA(obj, std::forward(args...)); + } + else + { + if (functional) return functional(std::forward(args...)); + } + return R(); + } + + protected: + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + enum { FUNC, FP, FPA } kind; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = obj; + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FP; + fn = std::forward(functional); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) __attribute__((always_inline)) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + if (fn) return fn(std::forward(args...)); + } + else + { + if (fnA) return fnA(obj, std::forward(args...)); + } + return R(); + } + + protected: + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + enum { FP, FPA } kind; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegatePImpl::fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + if (fn) return fn(std::forward(args...)); + } + else + { + if (functional) return functional(std::forward(args...)); + } + return R(); + } + + protected: + union { + FunctionType functional; + FunPtr fn; + }; + enum { FUNC, FP } kind; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + fn = del.fn; + } + + DelegatePImpl(DelegatePImpl&& del) + { + fn = std::move(del.fn); + } + + DelegatePImpl(FunPtr fn) + { + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F fn) + { + DelegatePImpl::fn = std::forward(fn); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + DelegatePImpl::fn = fn; + return *this; + } + + inline DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) + { + fn = nullptr; + return *this; + } + + inline IRAM_ATTR operator bool() const __attribute__((always_inline)) + { + return fn; + } + + operator FunVPPtr() const + { + return vPtrToFunPtrExec; + } + + void* arg() const + { + return reinterpret_cast(fn); + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + inline R IRAM_ATTR operator()(P... args) const __attribute__((always_inline)) + { + if (fn) return fn(std::forward(args...)); + return R(); + } + + protected: + FunPtr fn; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self) __attribute__((always_inline)) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this]() { return fnA(obj); }; + } + else + { + return functional; + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + if (fn) return fn(); + } + else if (FPA == kind) + { + if (fnA) return fnA(obj); + } + else + { + if (functional) return functional(); + } + return R(); + } + + protected: + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + enum { FUNC, FP, FPA } kind; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = obj; + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + kind = FP; + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self) __attribute__((always_inline)) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + if (fn) return fn(); + } + else + { + if (fnA) return fnA(obj); + } + return R(); + } + + protected: + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + enum { FP, FPA } kind; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegateImpl::fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + IRAM_ATTR operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + if (fn) return fn(); + } + else + { + if (functional) return functional(); + } + return R(); + } + + protected: + union { + FunctionType functional; + FunPtr fn; + }; + enum { FUNC, FP } kind; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + fn = del.fn; + } + + DelegateImpl(DelegateImpl&& del) + { + fn = std::move(del.fn); + } + + DelegateImpl(FunPtr fn) + { + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + DelegateImpl::fn = fn; + return *this; + } + + inline DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) + { + fn = nullptr; + return *this; + } + + inline IRAM_ATTR operator bool() const __attribute__((always_inline)) + { + return fn; + } + + operator FunVPPtr() const + { + return reinterpret_cast(fn); + } + + void* arg() const + { + return nullptr; + } + + /// Calling is safe without checking for nullptr. + /// If non-void, returns the default value. + /// In ISR context, where faults and exceptions must not + /// occurs, this saves the extra check for nullptr, + /// and allows the compiler to optimize out checks + /// in std::function which may not be ISR-safe or + /// cause linker errors, like l32r relocation errors + /// on the Xtensa ISA. + inline R IRAM_ATTR operator()() const __attribute__((always_inline)) + { + if (fn) return fn(); + return R(); + } + + protected: + FunPtr fn; + }; +#endif + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return reinterpret_cast(detail::DelegatePImpl::fnA); + } + else + { + return detail::DelegatePImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return detail::DelegatePImpl::obj; + } + else + { + return detail::DelegatePImpl::arg(); + } + } + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*, P...); + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegateImpl::DelegateImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return reinterpret_cast(detail::DelegateImpl::fnA); + } + else + { + return detail::DelegateImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return detail::DelegateImpl::obj; + } + else + { + return detail::DelegateImpl::arg(); + } + } + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*); + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + } +} + +template class Delegate; +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, const A& obj) : delegate::detail::Delegate::Delegate(fnA, obj) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, A&& obj) : delegate::detail::Delegate::Delegate(fnA, std::move(obj)) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; + +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + inline Delegate& IRAM_ATTR operator=(std::nullptr_t) __attribute__((always_inline)) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; + +#endif // __Delegate_h diff --git a/components/espsoftwareserial/circular_queue/MultiDelegate.h b/components/espsoftwareserial/circular_queue/MultiDelegate.h new file mode 100644 index 0000000..aee0741 --- /dev/null +++ b/components/espsoftwareserial/circular_queue/MultiDelegate.h @@ -0,0 +1,567 @@ +/* +MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate +class +Copyright (c) 2019-2020 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MULTIDELEGATE_H +#define __MULTIDELEGATE_H + +#include +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#else +#include "circular_queue/ghostl.h" +#endif + +#if defined(ESP8266) +#include +using esp8266::InterruptLock; +#elif defined(ARDUINO) +class InterruptLock { +public: + InterruptLock() { + noInterrupts(); + } + ~InterruptLock() { + interrupts(); + } +}; +#else +#include +#endif + +namespace +{ + + template< typename Delegate, typename R, bool ISQUEUE = false, typename... P> + struct CallP + { + static R execute(Delegate& del, P... args) + { + return del(std::forward(args...)); + } + }; + + template< typename Delegate, bool ISQUEUE, typename... P> + struct CallP + { + static bool execute(Delegate& del, P... args) + { + del(std::forward(args...)); + return true; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE = false> + struct Call + { + static R execute(Delegate& del) + { + return del(); + } + }; + + template< typename Delegate, bool ISQUEUE> + struct Call + { + static bool execute(Delegate& del) + { + del(); + return true; + } + }; + +} + +namespace delegate +{ + namespace detail + { + + template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P> + class MultiDelegatePImpl + { + public: + MultiDelegatePImpl() = default; + ~MultiDelegatePImpl() + { + *this = nullptr; + } + + MultiDelegatePImpl(const MultiDelegatePImpl&) = delete; + MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete; + + MultiDelegatePImpl(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + } + + MultiDelegatePImpl(const Delegate& del) + { + add(del); + } + + MultiDelegatePImpl(Delegate&& del) + { + add(std::move(del)); + } + + MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + return *this; + } + + MultiDelegatePImpl& operator=(std::nullptr_t) + { + if (last) + last->mNext = unused; + if (first) + unused = first; + while (unused) + { + auto to_delete = unused; + unused = unused->mNext; + delete(to_delete); + } + return *this; + } + + MultiDelegatePImpl& operator+=(const Delegate& del) + { + add(del); + return *this; + } + + MultiDelegatePImpl& operator+=(Delegate&& del) + { + add(std::move(del)); + return *this; + } + + protected: + struct Node_t + { + ~Node_t() + { + mDelegate = nullptr; // special overload in Delegate + } + Node_t* mNext = nullptr; + Delegate mDelegate; + }; + + Node_t* first = nullptr; + Node_t* last = nullptr; + Node_t* unused = nullptr; + size_t nodeCount = 0; + + // Returns a pointer to an unused Node_t, + // or if none are available allocates a new one, + // or nullptr if limit is reached + Node_t* IRAM_ATTR get_node_unsafe() + { + Node_t* result = nullptr; + // try to get an item from unused items list + if (unused) + { + result = unused; + unused = unused->mNext; + } + // if no unused items, and count not too high, allocate a new one + else if (nodeCount < QUEUE_CAPACITY) + { +#if defined(ESP8266) || defined(ESP32) + result = new (std::nothrow) Node_t; +#else + result = new Node_t; +#endif + if (result) + ++nodeCount; + } + return result; + } + + void recycle_node_unsafe(Node_t* node) + { + node->mDelegate = nullptr; // special overload in Delegate + node->mNext = unused; + unused = node; + } + +#ifndef ARDUINO + std::mutex mutex_unused; +#endif + public: + class iterator : public std::iterator + { + public: + Node_t* current = nullptr; + Node_t* prev = nullptr; + const Node_t* stop = nullptr; + + iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {} + iterator() = default; + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; + iterator& operator=(iterator&&) = default; + operator bool() const + { + return current && stop; + } + bool operator==(const iterator& rhs) const + { + return current == rhs.current; + } + bool operator!=(const iterator& rhs) const + { + return !operator==(rhs); + } + Delegate& operator*() const + { + return current->mDelegate; + } + Delegate* operator->() const + { + return ¤t->mDelegate; + } + iterator& operator++() // prefix + { + if (current && stop != current) + { + prev = current; + current = current->mNext; + } + else + current = nullptr; // end + return *this; + } + iterator& operator++(int) // postfix + { + iterator tmp(*this); + operator++(); + return tmp; + } + }; + + iterator begin() + { + return iterator(*this); + } + iterator end() const + { + return iterator(); + } + + const Delegate* add(const Delegate& del) + { + return add(Delegate(del)); + } + + const Delegate* add(Delegate&& del) + { + if (!del) + return nullptr; + +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + + Node_t* item = ISQUEUE ? get_node_unsafe() : +#if defined(ESP8266) || defined(ESP32) + new (std::nothrow) Node_t; +#else + new Node_t; +#endif + if (!item) + return nullptr; + + item->mDelegate = std::move(del); + item->mNext = nullptr; + + if (last) + last->mNext = item; + else + first = item; + last = item; + + return &item->mDelegate; + } + + iterator erase(iterator it) + { + if (!it) + return end(); +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + auto to_recycle = it.current; + + if (last == it.current) + last = it.prev; + it.current = it.current->mNext; + if (it.prev) + { + it.prev->mNext = it.current; + } + else + { + first = it.current; + } + if (ISQUEUE) + recycle_node_unsafe(to_recycle); + else + delete to_recycle; + return it; + } + + bool erase(const Delegate* const del) + { + auto it = begin(); + while (it) + { + if (del == &(*it)) + { + erase(it); + return true; + } + ++it; + } + return false; + } + + operator bool() const + { + return first; + } + + R operator()(P... args) + { + auto it = begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = CallP::execute(*it, args...); + if (result && ISQUEUE) + it = erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> + class MultiDelegateImpl : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + R operator()() + { + auto it = this->begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = Call::execute(*it); + if (result && ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + void operator()(P... args) + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + CallP::execute(*it, args...); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + + void operator()() + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + Call::execute(*it); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + } + +} + +/** +The MultiDelegate class template can be specialized to either a queue or an event multiplexer. +It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function. +@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on. +@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true), + the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain. + This is exploited to minimize the use of new and delete by reusing already allocated items, thus + reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are + used for allocation of the event handler items. + If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue + removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until + explicitly removed. + If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue + the type-conversion to bool of that result determines if the item is immediately removed or kept + after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event + handlers until they are explicitly removed. +@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically + allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate + instance during its own lifetime for efficiency. +*/ +template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> +class MultiDelegate : public delegate::detail::MultiDelegate +{ +public: + using delegate::detail::MultiDelegate::MultiDelegate; +}; + +#endif // __MULTIDELEGATE_H diff --git a/components/espsoftwareserial/circular_queue/circular_queue.h b/components/espsoftwareserial/circular_queue/circular_queue.h new file mode 100644 index 0000000..0d9a272 --- /dev/null +++ b/components/espsoftwareserial/circular_queue/circular_queue.h @@ -0,0 +1,393 @@ +/* +circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_h +#define __circular_queue_h + +#ifdef ARDUINO +#include +#endif + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#include +#include +#include "Delegate.h" +using std::min; +#else +#include "ghostl.h" +#endif + +#if !defined(ESP32) && !defined(ESP8266) +#define IRAM_ATTR +#endif + +/*! + @brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producer and consumer for the available(), peek(), + pop(), and push() type functions. +*/ +template< typename T, typename ForEachArg = void > +class circular_queue +{ +public: + /*! + @brief Constructs a valid, but zero-capacity dummy queue. + */ + circular_queue() : m_bufSize(1) + { + m_inPos.store(0); + m_outPos.store(0); + } + /*! + @brief Constructs a queue of the given maximum capacity. + */ + circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize]) + { + m_inPos.store(0); + m_outPos.store(0); + } + circular_queue(circular_queue&& cq) : + m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load()) + {} + ~circular_queue() + { + m_buffer.reset(); + } + circular_queue(const circular_queue&) = delete; + circular_queue& operator=(circular_queue&& cq) + { + m_bufSize = cq.m_bufSize; + m_buffer = cq.m_buffer; + m_inPos.store(cq.m_inPos.load()); + m_outPos.store(cq.m_outPos.load()); + } + circular_queue& operator=(const circular_queue&) = delete; + + /*! + @brief Get the numer of elements the queue can hold at most. + */ + size_t capacity() const + { + return m_bufSize - 1; + } + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free and concurrent producer or consumer access + will lead to corruption. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap); + + /*! + @brief Discard all data in the queue. + */ + void flush() + { + m_outPos.store(m_inPos.load()); + } + + /*! + @brief Get a snapshot number of elements that can be retrieved by pop. + */ + size_t IRAM_ATTR available() const + { + int avail = static_cast(m_inPos.load() - m_outPos.load()); + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Get the remaining free elementes for pushing. + */ + size_t IRAM_ATTR available_for_push() const + { + int avail = static_cast(m_outPos.load() - m_inPos.load()) - 1; + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Peek at the next element pop will return without removing it from the queue. + @return An rvalue copy of the next element that can be popped. If the queue is empty, + return an rvalue copy of the element that is pending the next push. + */ + T peek() const + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[outPos]; + } + + /*! + @brief Peek at the next pending input value. + @return A reference to the next element that can be pushed. + */ + T& IRAM_ATTR pushpeek() + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[inPos]; + } + + /*! + @brief Release the next pending input value, accessible by pushpeek(), into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push() + { + const auto inPos = m_inPos.load(std::memory_order_acquire); + const size_t next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_inPos.store(next, std::memory_order_release); + return true; + } + + /*! + @brief Move the rvalue parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val) + { + const auto inPos = m_inPos.load(std::memory_order_acquire); + const size_t next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_buffer[inPos] = std::move(val); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return true; + } + + /*! + @brief Push a copy of the parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { + T v(val); + return push(std::move(v)); + } + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size); +#endif + + /*! + @brief Pop the next available element from the queue. + @return An rvalue copy of the popped element, or a default + value of type T if the queue is empty. + */ + T pop(); + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Pop multiple elements in ordered sequence from the queue to a buffer. + If buffer is nullptr, simply discards up to size elements from the queue. + @return The number of elements actually popped from the queue to + buffer. + */ + size_t pop_n(T* buffer, size_t size); +#endif + + /*! + @brief Iterate over and remove each available element from queue, + calling back fun with an rvalue reference of every single element. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + void for_each(const Delegate& fun); +#else + void for_each(Delegate fun); +#endif + + /*! + @brief In reverse order, iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + bool for_each_rev_requeue(const Delegate& fun); +#else + bool for_each_rev_requeue(Delegate fun); +#endif + +protected: + const T defaultValue {}; + size_t m_bufSize; +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + std::unique_ptr m_buffer; +#else + std::unique_ptr m_buffer; +#endif + std::atomic m_inPos; + std::atomic m_outPos; +}; + +template< typename T, typename ForEachArg > +bool circular_queue::capacity(const size_t cap) +{ + if (cap + 1 == m_bufSize) return true; + else if (available() > cap) return false; + std::unique_ptr buffer(new T[cap + 1]); + const auto available = pop_n(buffer, cap); + m_buffer.reset(buffer); + m_bufSize = cap + 1; + std::atomic_thread_fence(std::memory_order_release); + m_inPos.store(available, std::memory_order_relaxed); + m_outPos.store(0, std::memory_order_release); + return true; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T, typename ForEachArg > +size_t circular_queue::push_n(const T* buffer, size_t size) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const auto outPos = m_outPos.load(std::memory_order_relaxed); + + size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos; + blockSize = min(size, blockSize); + if (!blockSize) return 0; + int next = (inPos + blockSize) % m_bufSize; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto dest = m_buffer.get() + inPos; + std::copy_n(std::make_move_iterator(buffer), blockSize, dest); + size = min(size - blockSize, outPos > 1 ? static_cast(outPos - next - 1) : 0); + next += size; + dest = m_buffer.get(); + std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return blockSize + size; +} +#endif + +template< typename T, typename ForEachArg > +T circular_queue::pop() +{ + const auto outPos = m_outPos.load(std::memory_order_acquire); + if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto val = std::move(m_buffer[outPos]); + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release); + return val; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T, typename ForEachArg > +size_t circular_queue::pop_n(T* buffer, size_t size) { + size_t avail = size = min(size, available()); + if (!avail) return 0; + const auto outPos = m_outPos.load(std::memory_order_acquire); + size_t n = min(avail, static_cast(m_bufSize - outPos)); + + std::atomic_thread_fence(std::memory_order_acquire); + + if (buffer) { + buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer); + avail -= n; + std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer); + } + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release); + return size; +} +#endif + +template< typename T, typename ForEachArg > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +void circular_queue::for_each(const Delegate& fun) +#else +void circular_queue::for_each(Delegate fun) +#endif +{ + auto outPos = m_outPos.load(std::memory_order_acquire); + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + while (outPos != inPos) + { + fun(std::move(m_buffer[outPos])); + std::atomic_thread_fence(std::memory_order_release); + outPos = (outPos + 1) % m_bufSize; + m_outPos.store(outPos, std::memory_order_release); + } +} + +template< typename T, typename ForEachArg > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +bool circular_queue::for_each_rev_requeue(const Delegate& fun) +#else +bool circular_queue::for_each_rev_requeue(Delegate fun) +#endif +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + auto pos = inPos0; + auto outPos1 = inPos0; + const auto posDecr = circular_queue::m_bufSize - 1; + do { + pos = (pos + posDecr) % circular_queue::m_bufSize; + T&& val = std::move(circular_queue::m_buffer[pos]); + if (fun(val)) + { + outPos1 = (outPos1 + posDecr) % circular_queue::m_bufSize; + if (outPos1 != pos) circular_queue::m_buffer[outPos1] = std::move(val); + } + } while (pos != outPos); + circular_queue::m_outPos.store(outPos1, std::memory_order_release); + return true; +} + +#endif // __circular_queue_h diff --git a/components/espsoftwareserial/circular_queue/circular_queue_mp.h b/components/espsoftwareserial/circular_queue/circular_queue_mp.h new file mode 100644 index 0000000..ba37689 --- /dev/null +++ b/components/espsoftwareserial/circular_queue/circular_queue_mp.h @@ -0,0 +1,200 @@ +/* +circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_mp_h +#define __circular_queue_mp_h + +#include "circular_queue.h" + +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif + +/*! + @brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producers and consumer for the available(), peek(), + pop(), and push() type functions, but is guarded to safely allow only a single producer + at any instant. +*/ +template< typename T, typename ForEachArg = void > +class circular_queue_mp : protected circular_queue +{ +public: + circular_queue_mp() = default; + circular_queue_mp(const size_t capacity) : circular_queue(capacity) + {} + circular_queue_mp(circular_queue&& cq) : circular_queue(std::move(cq)) + {} + using circular_queue::operator=; + using circular_queue::capacity; + using circular_queue::flush; + using circular_queue::available; + using circular_queue::available_for_push; + using circular_queue::peek; + using circular_queue::pop; + using circular_queue::pop_n; + using circular_queue::for_each; + using circular_queue::for_each_rev_requeue; + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free, but safe, concurrent producer or consumer access + is guarded. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::capacity(cap); + } + + bool IRAM_ATTR push() = delete; + + /*! + @brief Move the rvalue parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(std::move(val)); + } + + /*! + @brief Push a copy of the parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(val); + } + + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. This is guarded for + multiple producers, push_n() is atomic. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push_n(buffer, size); + } + + /*! + @brief Pops the next available element from the queue, requeues + it immediately. + @return A reference to the just requeued element, or the default + value of type T if the queue is empty. + */ + T& pop_requeue(); + + /*! + @brief Iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ + bool for_each_requeue(const Delegate& fun); + +#ifndef ESP8266 +protected: + std::mutex m_pushMtx; +#endif +}; + +template< typename T, typename ForEachArg > +T& circular_queue_mp::pop_requeue() +{ +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + const auto outPos = circular_queue::m_outPos.load(std::memory_order_acquire); + const auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (inPos == outPos) return circular_queue::defaultValue; + T& val = circular_queue::m_buffer[inPos] = std::move(circular_queue::m_buffer[outPos]); + const auto bufSize = circular_queue::m_bufSize; + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed); + circular_queue::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release); + return val; +} + +template< typename T, typename ForEachArg > +bool circular_queue_mp::for_each_requeue(const Delegate& fun) +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + do { + T&& val = std::move(circular_queue::m_buffer[outPos]); + if (fun(val)) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + std::atomic_thread_fence(std::memory_order_release); + auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + circular_queue::m_buffer[inPos] = std::move(val); + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_inPos.store((inPos + 1) % circular_queue::m_bufSize, std::memory_order_release); + } + else + { + std::atomic_thread_fence(std::memory_order_release); + } + outPos = (outPos + 1) % circular_queue::m_bufSize; + circular_queue::m_outPos.store(outPos, std::memory_order_release); + } while (outPos != inPos0); + return true; +} + +#endif // __circular_queue_mp_h diff --git a/components/espsoftwareserial/circular_queue/ghostl.h b/components/espsoftwareserial/circular_queue/ghostl.h new file mode 100644 index 0000000..ba3f863 --- /dev/null +++ b/components/espsoftwareserial/circular_queue/ghostl.h @@ -0,0 +1,94 @@ +/* +ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell + that allows building some Arduino ESP8266/ESP32 + libraries on Aruduino AVR. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ghostl_h +#define __ghostl_h + +#if defined(ARDUINO_ARCH_SAMD) +#include +#endif + +using size_t = decltype(sizeof(char)); + +namespace std +{ +#if !defined(ARDUINO_ARCH_SAMD) + typedef enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_seq_cst + } memory_order; + template< typename T > class atomic { + private: + T value; + public: + atomic() {} + atomic(T desired) { value = desired; } + void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; } + T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; } + }; + inline void atomic_thread_fence(std::memory_order order) noexcept {} + template< typename T > T&& move(T& t) noexcept { return static_cast(t); } +#endif + + template< typename T, size_t long N > struct array + { + T _M_elems[N]; + decltype(sizeof(0)) size() const { return N; } + T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; } + const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; } + }; + + template< typename T > class unique_ptr + { + public: + using pointer = T*; + unique_ptr() noexcept : ptr(nullptr) {} + unique_ptr(pointer p) : ptr(p) {} + pointer operator->() const noexcept { return ptr; } + T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; } + void reset(pointer p = pointer()) noexcept + { + delete ptr; + ptr = p; + } + T& operator*() const { return *ptr; } + private: + pointer ptr; + }; + + template< typename T > using function = T*; + using nullptr_t = decltype(nullptr); + + template + struct identity { + typedef T type; + }; + + template + inline T&& forward(typename identity::type& t) noexcept + { + return static_cast::type&&>(t); + } +} + +#endif // __ghostl_h diff --git a/components/ratgdo/__init__.py b/components/ratgdo/__init__.py index 3ddc120..46c99c6 100644 --- a/components/ratgdo/__init__.py +++ b/components/ratgdo/__init__.py @@ -2,14 +2,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID from esphome import pins, automation -from esphome.components import uart -DEPENDENCIES = ["preferences", "uart"] +DEPENDENCIES = ["preferences"] MULTI_CONF = True ratgdo_ns = cg.esphome_ns.namespace("ratgdo") -RATGDO = ratgdo_ns.class_("RATGDOComponent", cg.Component, uart.UARTDevice) +RATGDO = ratgdo_ns.class_("RATGDOComponent", cg.Component) CONF_OUTPUT_GDO = "output_gdo_pin" @@ -105,10 +104,13 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_STATUS_OBST]) cg.add(var.set_status_obst_pin(pin)) - await uart.register_uart_device(var, config) - cg.add_library( name="secplus", repository="https://github.com/bdraco/secplus", version="f98c3220356c27717a25102c0b35815ebbd26ccc", ) + cg.add_library( + name="espsoftwareserial", + repository="https://github.com/bdraco/espsoftwareserial", + version="2f408224633316b997f82339e5b2731b1e561060", + ) \ No newline at end of file diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp index c2d5ef1..8343dcd 100644 --- a/components/ratgdo/ratgdo.cpp +++ b/components/ratgdo/ratgdo.cpp @@ -126,7 +126,7 @@ namespace ratgdo { this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT); - this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); + this->swSerial.begin(9600, SWSERIAL_8N1, this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin(), true); this->trigger_open_pin_->attach_interrupt(RATGDOStore::isrDoorOpen, &this->store_, gpio::INTERRUPT_ANY_EDGE); this->trigger_close_pin_->attach_interrupt(RATGDOStore::isrDoorClose, &this->store_, gpio::INTERRUPT_ANY_EDGE); @@ -342,16 +342,11 @@ namespace ratgdo { void RATGDOComponent::gdoStateLoop() { - if (!this->available()) { + if (!this->swSerial.available()) { // ESP_LOGD(TAG, "No data available input:%d output:%d", this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin()); return; } - uint8_t serData; - if (!this->read_byte(&serData)) { - ESP_LOGD(TAG, "Failed to read byte"); - return; - } - + uint8_t serData = this->swSerial.read(); static uint32_t msgStart; static bool reading = false; static uint16_t byteCount = 0; @@ -484,7 +479,7 @@ namespace ratgdo { this->output_gdo_pin_->digital_write(false); // bring the line low delayMicroseconds(1260); // "LOW" pulse duration before the message start - this->write_array(this->txRollingCode, CODE_LENGTH); + this->swSerial.write(this->txRollingCode, CODE_LENGTH); } void RATGDOComponent::sync() diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h index 6269d19..ec12fb8 100644 --- a/components/ratgdo/ratgdo.h +++ b/components/ratgdo/ratgdo.h @@ -12,11 +12,11 @@ ************************************/ #pragma once -#include "esphome/components/uart/uart.h" #include "esphome/core/component.h" #include "esphome/core/gpio.h" #include "esphome/core/log.h" #include "esphome/core/preferences.h" +#include "SoftwareSerial.h" // Using espsoftwareserial https://github.com/plerup/espsoftwareserial extern "C" { #include "secplus.h" @@ -79,6 +79,8 @@ namespace ratgdo { /********************************** GLOBAL VARS * *****************************************/ uint32_t rollingCodeCounter; + EspSoftwareSerial::UART swSerial; + uint8_t txRollingCode[CODE_LENGTH]; uint8_t rxRollingCode[CODE_LENGTH];