Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

597 lines
14 KiB

/*
UART MODULE
Copyright (C) 2022 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "espurna.h"
#if UART_SUPPORT
#include "uart.h"
#include "utils.h"
#if UART_SOFTWARE_SUPPORT
#include <SoftwareSerial.h>
#endif
#include <array>
#include <utility>
namespace espurna {
namespace driver {
namespace uart {
namespace {
enum class Parity {
None,
Even,
Odd,
};
struct Config {
uint8_t data_bits;
Parity parity;
uint8_t stop_bits;
};
} // namespace
} // namespace uart
} // namespace driver
namespace settings {
namespace internal {
namespace {
PROGMEM_STRING(ParityNone, "none");
PROGMEM_STRING(ParityEven, "even");
PROGMEM_STRING(ParityOdd, "odd");
static constexpr std::array<settings::options::Enumeration<driver::uart::Parity>, 3> ParityOptions PROGMEM {
{{driver::uart::Parity::None, ParityNone},
{driver::uart::Parity::Even, ParityEven},
{driver::uart::Parity::Odd, ParityOdd}}
};
} // namespace
template <>
driver::uart::Parity convert(const String& value) {
return convert(ParityOptions, value, driver::uart::Parity::None);
}
String serialize(driver::uart::Parity value) {
return espurna::settings::internal::serialize(ParityOptions, value);
}
} // namespace internal
} // namespace settings
namespace driver {
namespace uart {
namespace {
namespace types {
using HardwareConfig = ::SerialConfig;
using HardwareMode = ::SerialMode;
#if UART_SOFTWARE_SUPPORT
#if defined(ARDUINO_ESP8266_RELEASE_2_7_2) \
|| defined(ARDUINO_ESP8266_RELEASE_2_7_3) \
|| defined(ARDUINO_ESP8266_RELEASE_2_7_4)
|| defined(ARDUINO_ESP8266_RELEASE_3_0_0) \
|| defined(ARDUINO_ESP8266_RELEASE_3_0_1) \
|| defined(ARDUINO_ESP8266_RELEASE_3_1_0) \
|| defined(ARDUINO_ESP8266_RELEASE_3_1_1)
using SoftwareConfig = ::SoftwareSerialConfig;
#elif defined(ARDUINO_ESP8266_RELEASE_3_1_2)
using SoftwareConfig = ::EspSoftwareSerial::Config;
#else
using SoftwareConfig = ::EspSoftwareSerial::Config;
#endif
#endif
} // namespace types
namespace build {
// i.e. uart0, uart1 and a single sw port
// for general use, hardware uart *should* be prefered method
constexpr size_t PortsMax { 3 };
// todo; technically, tx==2 is also possible
// but, we reserve that for the uart1 TX-only interface
constexpr bool uart0_normal(uint8_t tx, uint8_t rx) {
return ((tx == 1) && (rx == 3))
|| ((tx == GPIO_NONE) && (rx == 3))
|| ((tx == 1) && (rx == GPIO_NONE));
}
constexpr bool uart0_swapped(uint8_t tx, uint8_t rx) {
return ((tx == 15) && (rx == 13))
|| ((tx == GPIO_NONE) && (rx == 13))
|| ((tx == 15) && (rx == GPIO_NONE));
}
constexpr bool uart1_normal(uint8_t tx, uint8_t rx) {
return (tx == 2) && (rx == GPIO_NONE);
}
constexpr uint8_t tx(size_t index) {
return (0 == index) ? (UART1_TX_PIN) :
(1 == index) ? (UART2_TX_PIN) :
(2 == index) ? (UART3_TX_PIN) :
GPIO_NONE;
}
constexpr uint8_t rx(size_t index) {
return (0 == index) ? (UART1_RX_PIN) :
(1 == index) ? (UART2_RX_PIN) :
(2 == index) ? (UART3_RX_PIN) :
GPIO_NONE;
}
constexpr uint32_t baudrate(size_t index) {
return (0 == index) ? (UART1_BAUDRATE) :
(1 == index) ? (UART2_BAUDRATE) :
(2 == index) ? (UART3_BAUDRATE) :
0;
}
constexpr uint8_t data_bits(size_t index) {
return (0 == index) ? (UART1_DATA_BITS) :
(1 == index) ? (UART2_DATA_BITS) :
(2 == index) ? (UART3_DATA_BITS)
: 0;
}
constexpr Parity parity(size_t index) {
return (0 == index) ? (Parity::UART1_PARITY) :
(1 == index) ? (Parity::UART2_PARITY) :
(2 == index) ? (Parity::UART3_PARITY)
: Parity::None;
}
constexpr uint8_t stop_bits(size_t index) {
return (0 == index) ? (UART1_STOP_BITS) :
(1 == index) ? (UART2_STOP_BITS) :
(2 == index) ? (UART3_STOP_BITS)
: 0;
}
constexpr bool invert(size_t index) {
return (0 == index) ? (UART1_INVERT == 1) :
(1 == index) ? (UART2_INVERT == 1) :
(2 == index) ? (UART3_INVERT == 1)
: false;
}
} // namespace build
constexpr int data_bits_from_config(uint8_t bits) {
return (bits == 5) ? 0 :
(bits == 6) ? 0b100 :
(bits == 7) ? 0b1000 :
(bits == 8) ? 0b1100 :
data_bits_from_config(8);
}
constexpr int parity_from_config(Parity parity) {
return (parity == Parity::None) ? 0 :
(parity == Parity::Even) ? 0b10 :
(parity == Parity::Odd) ? 0b11 :
parity_from_config(Parity::None);
}
constexpr int stop_bits_from_config(uint8_t bits) {
return (bits == 1) ? 0b10000 :
(bits == 2) ? 0b110000 :
stop_bits_from_config(1);
}
template <typename T>
constexpr T from_config(Config);
template <>
constexpr types::HardwareConfig from_config(Config config) {
return static_cast<types::HardwareConfig>(
data_bits_from_config(config.data_bits)
| parity_from_config(config.parity)
| stop_bits_from_config(config.stop_bits));
}
namespace settings {
namespace keys {
PROGMEM_STRING(TxPin, "uartTx");
PROGMEM_STRING(RxPin, "uartRx");
PROGMEM_STRING(Baudrate, "uartBaud");
PROGMEM_STRING(DataBits, "uartDataBits");
PROGMEM_STRING(StopBits, "uartStopBits");
PROGMEM_STRING(Parity, "uartParity");
PROGMEM_STRING(Invert, "uartInv");
} // namespace keys
uint8_t tx(size_t index) {
return getSetting({keys::TxPin, index}, build::tx(index));
}
uint8_t rx(size_t index) {
return getSetting({keys::RxPin, index}, build::rx(index));
}
uint32_t baudrate(size_t index) {
return getSetting({keys::Baudrate, index}, build::baudrate(index));
}
uint8_t data_bits(size_t index) {
return getSetting({keys::DataBits, index}, build::data_bits(index));
}
uint8_t stop_bits(size_t index) {
return getSetting({keys::StopBits, index}, build::stop_bits(index));
}
Parity parity(size_t index) {
return getSetting({keys::Parity, index}, build::parity(index));
}
bool invert(size_t index) {
return getSetting({keys::Invert, index}, build::invert(index));
}
} // namespace settings
using StreamPtr = std::unique_ptr<Stream>;
struct BasePort {
Type type;
bool tx;
bool rx;
StreamPtr stream;
};
using BasePortPtr = std::unique_ptr<BasePort>;
namespace internal {
BasePortPtr ports[build::PortsMax];
bool used_hardware_ports[2] = {false, false};
} // namespace internal
BasePortPtr hardware_port(
uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert)
{
const int number =
build::uart0_normal(tx, rx)
? 0 :
build::uart0_swapped(tx, rx)
? 0 :
build::uart1_normal(tx, rx)
? 1
: -1;
if ((number < 0) || (internal::used_hardware_ports[number])) {
return nullptr;
}
const int mode =
(tx == GPIO_NONE)
? SERIAL_RX_ONLY :
(rx == GPIO_NONE)
? SERIAL_TX_ONLY :
((tx != GPIO_NONE) && (rx != GPIO_NONE))
? SERIAL_FULL
: -1;
if (mode < 0) {
return nullptr;
}
internal::used_hardware_ports[number] = true;
auto* ptr = new HardwareSerial(number);
ptr->begin(baudrate,
from_config<types::HardwareConfig>(config),
static_cast<types::HardwareMode>(mode),
tx, invert);
if ((number == 0) && (build::uart0_swapped(tx, rx))) {
ptr->flush();
ptr->swap();
}
return std::make_unique<BasePort>(
BasePort{
.type = (number == 0) ? Type::Uart0 : Type::Uart1,
.tx = (tx != GPIO_NONE),
.rx = (rx != GPIO_NONE),
.stream = StreamPtr(ptr),
});
}
// based on the values in v6 of the lib. still, return bits instead of the octal notation used there
#if UART_SOFTWARE_SUPPORT
constexpr int software_serial_data_bits_from_config(uint8_t bits) {
return (bits == 5) ? 0 :
(bits == 6) ? 0b1 :
(bits == 7) ? 0b10 :
(bits == 8) ? 0b11 :
software_serial_data_bits_from_config(8);
}
// btw, SoftwareSerial also has Mark and Space
// no support on the hardware peripheral though (afaik)
constexpr int software_serial_parity_from_config(Parity parity) {
return (parity == Parity::None) ? 0 :
(parity == Parity::Even) ? 0b10000 :
(parity == Parity::Odd) ? 0b11000 :
software_serial_parity_from_config(Parity::None);
}
constexpr int software_serial_stop_bits_from_config(uint8_t bits) {
return (bits == 1) ? 0b0 :
(bits == 2) ? 0b10000000 :
software_serial_stop_bits_from_config(1);
}
template <>
constexpr types::SoftwareConfig from_config(Config config) {
return static_cast<types::SoftwareConfig>(
software_serial_data_bits_from_config(config.data_bits)
| software_serial_parity_from_config(config.parity)
| software_serial_stop_bits_from_config(config.stop_bits));
}
BasePortPtr software_serial_port(
uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert)
{
const int8_t tx_pin = (tx == GPIO_NONE) ? -1 : tx;
const int8_t rx_pin = (rx == GPIO_NONE) ? -1 : rx;
auto* ptr = new SoftwareSerial(rx_pin, tx_pin, invert);
ptr->begin(baudrate, from_config<types::SoftwareConfig>(config));
return std::make_unique<BasePort>(
BasePort{
.type = Type::Software,
.tx = (tx_pin > 0),
.rx = (rx_pin > 0),
.stream = StreamPtr(ptr),
});
}
#endif
BasePortPtr make_port(size_t index) {
BasePortPtr out;
const auto tx = settings::tx(index);
const auto rx = settings::rx(index);
if ((tx == GPIO_NONE) && (rx == GPIO_NONE)) {
return out;
}
if ((tx != GPIO_NONE) && !gpioLock(tx)) {
return out;
}
if ((rx != GPIO_NONE) && !gpioLock(rx)) {
gpioUnlock(tx);
return out;
}
const auto config = Config{
.data_bits = settings::data_bits(index),
.parity = settings::parity(index),
.stop_bits = settings::stop_bits(index),
};
const auto baudrate = settings::baudrate(index);
const auto invert = settings::invert(index);
if (build::uart0_normal(tx, rx)
|| build::uart0_swapped(tx, rx)
|| build::uart1_normal(tx, rx))
{
out = hardware_port(baudrate, tx, rx, config, invert);
}
#if UART_SOFTWARE_SUPPORT
if (!out) {
out = software_serial_port(baudrate, tx, rx, config, invert);
}
#endif
return out;
}
size_t ports() {
size_t out = 0;
for (const auto& port : internal::ports) {
if (!port) {
break;
}
++out;
}
return out;
}
namespace settings {
namespace query {
#define ID_VALUE(NAME)\
String NAME (size_t id) {\
return espurna::settings::internal::serialize(\
espurna::driver::uart::settings::NAME(id));\
}
ID_VALUE(tx)
ID_VALUE(rx)
ID_VALUE(baudrate)
ID_VALUE(data_bits)
ID_VALUE(stop_bits)
ID_VALUE(parity)
ID_VALUE(invert)
#undef ID_VALUE
static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROGMEM {
{keys::TxPin, query::tx},
{keys::RxPin, query::rx},
{keys::Baudrate, query::baudrate},
{keys::DataBits, query::data_bits},
{keys::StopBits, query::stop_bits},
{keys::Parity, query::parity},
{keys::Invert, query::invert},
};
bool checkSamePrefix(StringView key) {
PROGMEM_STRING(Prefix, "uart");
return espurna::settings::query::samePrefix(key, Prefix);
}
String findIndexedValueFrom(StringView key) {
return espurna::settings::query::IndexedSetting::findValueFrom(
ports(), IndexedSettings, key);
}
void setup() {
settingsRegisterQueryHandler({
.check = checkSamePrefix,
.get = findIndexedValueFrom,
});
}
} // namespace query
} // namespace settings
#if TERMINAL_SUPPORT
namespace terminal {
namespace commands {
String port_type(Type type) {
const char* out = PSTR("UNKNOWN");
switch (type) {
case Type::Unknown:
break;
case Type::Software:
out = PSTR("SOFTWARE");
break;
case Type::Uart0:
out = PSTR("UART0");
break;
case Type::Uart1:
out = PSTR("UART1");
break;
}
return out;
}
void uart(::terminal::CommandContext&& ctx) {
if (ctx.argv.size() == 1) {
for (size_t index = 0; index < std::size(internal::ports); ++index) {
const auto& port = internal::ports[index];
if (!port) {
break;
}
ctx.output.printf_P(
PSTR("%zu - %s{tx=%c rx=%c}\n"),
index, port_type(port->type).c_str(),
port->tx ? 'y' : 'n',
port->rx ? 'y' : 'n');
}
} else if (ctx.argv.size() == 2) {
const auto result = parseUnsigned(ctx.argv[1], 10);
if (!result.ok) {
terminalError(ctx, F("Invalid ID"));
return;
}
if (result.value >= ports()) {
ctx.output.print(F("(Not active)"));
}
settingsDump(ctx, settings::query::IndexedSettings, result.value);
} else {
terminalError(ctx, F("UART [<ID>]"));
return;
}
terminalOK(ctx);
}
PROGMEM_STRING(Uart, "UART");
static constexpr ::terminal::Command List[] PROGMEM {
{Uart, commands::uart},
};
} // namespace commands
void setup() {
espurna::terminal::add(commands::List);
}
} // namespace terminal
#endif
PortPtr port(size_t index) {
const auto& ptr = internal::ports[index];
if ((index < std::size(internal::ports)) && (ptr)) {
return std::make_unique<Port>(
Port{
.type = ptr->type,
.tx = ptr->tx,
.rx = ptr->rx,
.stream = ptr->stream.get(),
});
}
return nullptr;
}
void setup() {
#if TERMINAL_SUPPORT
terminal::setup();
#endif
settings::query::setup();
for (size_t index = 0; index < build::PortsMax; ++index) {
auto& port = internal::ports[index];
port = make_port(index);
if (!port) {
break;
}
}
}
} // namespace uart
} // namespace
} // namespace driver
} // namespace espurna
espurna::driver::uart::PortPtr uartPort(size_t index) {
return espurna::driver::uart::port(index);
}
void uartSetup() {
espurna::driver::uart::setup();
}
#endif // UART_SUPPORT