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

  1. /*
  2. UART MODULE
  3. Copyright (C) 2022 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. */
  5. #include "espurna.h"
  6. #if UART_SUPPORT
  7. #include "uart.h"
  8. #include "utils.h"
  9. #if UART_SOFTWARE_SUPPORT
  10. #include <SoftwareSerial.h>
  11. #endif
  12. #include <array>
  13. #include <utility>
  14. namespace espurna {
  15. namespace driver {
  16. namespace uart {
  17. namespace {
  18. enum class Parity {
  19. None,
  20. Even,
  21. Odd,
  22. };
  23. struct Config {
  24. uint8_t data_bits;
  25. Parity parity;
  26. uint8_t stop_bits;
  27. };
  28. } // namespace
  29. } // namespace uart
  30. } // namespace driver
  31. namespace settings {
  32. namespace internal {
  33. namespace {
  34. PROGMEM_STRING(ParityNone, "none");
  35. PROGMEM_STRING(ParityEven, "even");
  36. PROGMEM_STRING(ParityOdd, "odd");
  37. static constexpr std::array<settings::options::Enumeration<driver::uart::Parity>, 3> ParityOptions PROGMEM {
  38. {{driver::uart::Parity::None, ParityNone},
  39. {driver::uart::Parity::Even, ParityEven},
  40. {driver::uart::Parity::Odd, ParityOdd}}
  41. };
  42. } // namespace
  43. template <>
  44. driver::uart::Parity convert(const String& value) {
  45. return convert(ParityOptions, value, driver::uart::Parity::None);
  46. }
  47. String serialize(driver::uart::Parity value) {
  48. return espurna::settings::internal::serialize(ParityOptions, value);
  49. }
  50. } // namespace internal
  51. } // namespace settings
  52. namespace driver {
  53. namespace uart {
  54. namespace {
  55. namespace types {
  56. using HardwareConfig = ::SerialConfig;
  57. using HardwareMode = ::SerialMode;
  58. #if UART_SOFTWARE_SUPPORT
  59. #if defined(ARDUINO_ESP8266_RELEASE_2_7_2) \
  60. || defined(ARDUINO_ESP8266_RELEASE_2_7_3) \
  61. || defined(ARDUINO_ESP8266_RELEASE_2_7_4)
  62. || defined(ARDUINO_ESP8266_RELEASE_3_0_0) \
  63. || defined(ARDUINO_ESP8266_RELEASE_3_0_1) \
  64. || defined(ARDUINO_ESP8266_RELEASE_3_1_0) \
  65. || defined(ARDUINO_ESP8266_RELEASE_3_1_1)
  66. using SoftwareConfig = ::SoftwareSerialConfig;
  67. #elif defined(ARDUINO_ESP8266_RELEASE_3_1_2)
  68. using SoftwareConfig = ::EspSoftwareSerial::Config;
  69. #else
  70. using SoftwareConfig = ::EspSoftwareSerial::Config;
  71. #endif
  72. #endif
  73. } // namespace types
  74. namespace build {
  75. // i.e. uart0, uart1 and a single sw port
  76. // for general use, hardware uart *should* be prefered method
  77. constexpr size_t PortsMax { 3 };
  78. // todo; technically, tx==2 is also possible
  79. // but, we reserve that for the uart1 TX-only interface
  80. constexpr bool uart0_normal(uint8_t tx, uint8_t rx) {
  81. return ((tx == 1) && (rx == 3))
  82. || ((tx == GPIO_NONE) && (rx == 3))
  83. || ((tx == 1) && (rx == GPIO_NONE));
  84. }
  85. constexpr bool uart0_swapped(uint8_t tx, uint8_t rx) {
  86. return ((tx == 15) && (rx == 13))
  87. || ((tx == GPIO_NONE) && (rx == 13))
  88. || ((tx == 15) && (rx == GPIO_NONE));
  89. }
  90. constexpr bool uart1_normal(uint8_t tx, uint8_t rx) {
  91. return (tx == 2) && (rx == GPIO_NONE);
  92. }
  93. constexpr uint8_t tx(size_t index) {
  94. return (0 == index) ? (UART1_TX_PIN) :
  95. (1 == index) ? (UART2_TX_PIN) :
  96. (2 == index) ? (UART3_TX_PIN) :
  97. GPIO_NONE;
  98. }
  99. constexpr uint8_t rx(size_t index) {
  100. return (0 == index) ? (UART1_RX_PIN) :
  101. (1 == index) ? (UART2_RX_PIN) :
  102. (2 == index) ? (UART3_RX_PIN) :
  103. GPIO_NONE;
  104. }
  105. constexpr uint32_t baudrate(size_t index) {
  106. return (0 == index) ? (UART1_BAUDRATE) :
  107. (1 == index) ? (UART2_BAUDRATE) :
  108. (2 == index) ? (UART3_BAUDRATE) :
  109. 0;
  110. }
  111. constexpr uint8_t data_bits(size_t index) {
  112. return (0 == index) ? (UART1_DATA_BITS) :
  113. (1 == index) ? (UART2_DATA_BITS) :
  114. (2 == index) ? (UART3_DATA_BITS)
  115. : 0;
  116. }
  117. constexpr Parity parity(size_t index) {
  118. return (0 == index) ? (Parity::UART1_PARITY) :
  119. (1 == index) ? (Parity::UART2_PARITY) :
  120. (2 == index) ? (Parity::UART3_PARITY)
  121. : Parity::None;
  122. }
  123. constexpr uint8_t stop_bits(size_t index) {
  124. return (0 == index) ? (UART1_STOP_BITS) :
  125. (1 == index) ? (UART2_STOP_BITS) :
  126. (2 == index) ? (UART3_STOP_BITS)
  127. : 0;
  128. }
  129. constexpr bool invert(size_t index) {
  130. return (0 == index) ? (UART1_INVERT == 1) :
  131. (1 == index) ? (UART2_INVERT == 1) :
  132. (2 == index) ? (UART3_INVERT == 1)
  133. : false;
  134. }
  135. } // namespace build
  136. constexpr int data_bits_from_config(uint8_t bits) {
  137. return (bits == 5) ? 0 :
  138. (bits == 6) ? 0b100 :
  139. (bits == 7) ? 0b1000 :
  140. (bits == 8) ? 0b1100 :
  141. data_bits_from_config(8);
  142. }
  143. constexpr int parity_from_config(Parity parity) {
  144. return (parity == Parity::None) ? 0 :
  145. (parity == Parity::Even) ? 0b10 :
  146. (parity == Parity::Odd) ? 0b11 :
  147. parity_from_config(Parity::None);
  148. }
  149. constexpr int stop_bits_from_config(uint8_t bits) {
  150. return (bits == 1) ? 0b10000 :
  151. (bits == 2) ? 0b110000 :
  152. stop_bits_from_config(1);
  153. }
  154. template <typename T>
  155. constexpr T from_config(Config);
  156. template <>
  157. constexpr types::HardwareConfig from_config(Config config) {
  158. return static_cast<types::HardwareConfig>(
  159. data_bits_from_config(config.data_bits)
  160. | parity_from_config(config.parity)
  161. | stop_bits_from_config(config.stop_bits));
  162. }
  163. namespace settings {
  164. namespace keys {
  165. PROGMEM_STRING(TxPin, "uartTx");
  166. PROGMEM_STRING(RxPin, "uartRx");
  167. PROGMEM_STRING(Baudrate, "uartBaud");
  168. PROGMEM_STRING(DataBits, "uartDataBits");
  169. PROGMEM_STRING(StopBits, "uartStopBits");
  170. PROGMEM_STRING(Parity, "uartParity");
  171. PROGMEM_STRING(Invert, "uartInv");
  172. } // namespace keys
  173. uint8_t tx(size_t index) {
  174. return getSetting({keys::TxPin, index}, build::tx(index));
  175. }
  176. uint8_t rx(size_t index) {
  177. return getSetting({keys::RxPin, index}, build::rx(index));
  178. }
  179. uint32_t baudrate(size_t index) {
  180. return getSetting({keys::Baudrate, index}, build::baudrate(index));
  181. }
  182. uint8_t data_bits(size_t index) {
  183. return getSetting({keys::DataBits, index}, build::data_bits(index));
  184. }
  185. uint8_t stop_bits(size_t index) {
  186. return getSetting({keys::StopBits, index}, build::stop_bits(index));
  187. }
  188. Parity parity(size_t index) {
  189. return getSetting({keys::Parity, index}, build::parity(index));
  190. }
  191. bool invert(size_t index) {
  192. return getSetting({keys::Invert, index}, build::invert(index));
  193. }
  194. } // namespace settings
  195. using StreamPtr = std::unique_ptr<Stream>;
  196. struct BasePort {
  197. Type type;
  198. bool tx;
  199. bool rx;
  200. StreamPtr stream;
  201. };
  202. using BasePortPtr = std::unique_ptr<BasePort>;
  203. namespace internal {
  204. BasePortPtr ports[build::PortsMax];
  205. bool used_hardware_ports[2] = {false, false};
  206. } // namespace internal
  207. BasePortPtr hardware_port(
  208. uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert)
  209. {
  210. const int number =
  211. build::uart0_normal(tx, rx)
  212. ? 0 :
  213. build::uart0_swapped(tx, rx)
  214. ? 0 :
  215. build::uart1_normal(tx, rx)
  216. ? 1
  217. : -1;
  218. if ((number < 0) || (internal::used_hardware_ports[number])) {
  219. return nullptr;
  220. }
  221. const int mode =
  222. (tx == GPIO_NONE)
  223. ? SERIAL_RX_ONLY :
  224. (rx == GPIO_NONE)
  225. ? SERIAL_TX_ONLY :
  226. ((tx != GPIO_NONE) && (rx != GPIO_NONE))
  227. ? SERIAL_FULL
  228. : -1;
  229. if (mode < 0) {
  230. return nullptr;
  231. }
  232. internal::used_hardware_ports[number] = true;
  233. auto* ptr = new HardwareSerial(number);
  234. ptr->begin(baudrate,
  235. from_config<types::HardwareConfig>(config),
  236. static_cast<types::HardwareMode>(mode),
  237. tx, invert);
  238. if ((number == 0) && (build::uart0_swapped(tx, rx))) {
  239. ptr->flush();
  240. ptr->swap();
  241. }
  242. return std::make_unique<BasePort>(
  243. BasePort{
  244. .type = (number == 0) ? Type::Uart0 : Type::Uart1,
  245. .tx = (tx != GPIO_NONE),
  246. .rx = (rx != GPIO_NONE),
  247. .stream = StreamPtr(ptr),
  248. });
  249. }
  250. // based on the values in v6 of the lib. still, return bits instead of the octal notation used there
  251. #if UART_SOFTWARE_SUPPORT
  252. constexpr int software_serial_data_bits_from_config(uint8_t bits) {
  253. return (bits == 5) ? 0 :
  254. (bits == 6) ? 0b1 :
  255. (bits == 7) ? 0b10 :
  256. (bits == 8) ? 0b11 :
  257. software_serial_data_bits_from_config(8);
  258. }
  259. // btw, SoftwareSerial also has Mark and Space
  260. // no support on the hardware peripheral though (afaik)
  261. constexpr int software_serial_parity_from_config(Parity parity) {
  262. return (parity == Parity::None) ? 0 :
  263. (parity == Parity::Even) ? 0b10000 :
  264. (parity == Parity::Odd) ? 0b11000 :
  265. software_serial_parity_from_config(Parity::None);
  266. }
  267. constexpr int software_serial_stop_bits_from_config(uint8_t bits) {
  268. return (bits == 1) ? 0b0 :
  269. (bits == 2) ? 0b10000000 :
  270. software_serial_stop_bits_from_config(1);
  271. }
  272. template <>
  273. constexpr types::SoftwareConfig from_config(Config config) {
  274. return static_cast<types::SoftwareConfig>(
  275. software_serial_data_bits_from_config(config.data_bits)
  276. | software_serial_parity_from_config(config.parity)
  277. | software_serial_stop_bits_from_config(config.stop_bits));
  278. }
  279. BasePortPtr software_serial_port(
  280. uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert)
  281. {
  282. const int8_t tx_pin = (tx == GPIO_NONE) ? -1 : tx;
  283. const int8_t rx_pin = (rx == GPIO_NONE) ? -1 : rx;
  284. auto* ptr = new SoftwareSerial(rx_pin, tx_pin, invert);
  285. ptr->begin(baudrate, from_config<types::SoftwareConfig>(config));
  286. return std::make_unique<BasePort>(
  287. BasePort{
  288. .type = Type::Software,
  289. .tx = (tx_pin > 0),
  290. .rx = (rx_pin > 0),
  291. .stream = StreamPtr(ptr),
  292. });
  293. }
  294. #endif
  295. BasePortPtr make_port(size_t index) {
  296. BasePortPtr out;
  297. const auto tx = settings::tx(index);
  298. const auto rx = settings::rx(index);
  299. if ((tx == GPIO_NONE) && (rx == GPIO_NONE)) {
  300. return out;
  301. }
  302. if ((tx != GPIO_NONE) && !gpioLock(tx)) {
  303. return out;
  304. }
  305. if ((rx != GPIO_NONE) && !gpioLock(rx)) {
  306. gpioUnlock(tx);
  307. return out;
  308. }
  309. const auto config = Config{
  310. .data_bits = settings::data_bits(index),
  311. .parity = settings::parity(index),
  312. .stop_bits = settings::stop_bits(index),
  313. };
  314. const auto baudrate = settings::baudrate(index);
  315. const auto invert = settings::invert(index);
  316. if (build::uart0_normal(tx, rx)
  317. || build::uart0_swapped(tx, rx)
  318. || build::uart1_normal(tx, rx))
  319. {
  320. out = hardware_port(baudrate, tx, rx, config, invert);
  321. }
  322. #if UART_SOFTWARE_SUPPORT
  323. if (!out) {
  324. out = software_serial_port(baudrate, tx, rx, config, invert);
  325. }
  326. #endif
  327. return out;
  328. }
  329. size_t ports() {
  330. size_t out = 0;
  331. for (const auto& port : internal::ports) {
  332. if (!port) {
  333. break;
  334. }
  335. ++out;
  336. }
  337. return out;
  338. }
  339. namespace settings {
  340. namespace query {
  341. #define ID_VALUE(NAME)\
  342. String NAME (size_t id) {\
  343. return espurna::settings::internal::serialize(\
  344. espurna::driver::uart::settings::NAME(id));\
  345. }
  346. ID_VALUE(tx)
  347. ID_VALUE(rx)
  348. ID_VALUE(baudrate)
  349. ID_VALUE(data_bits)
  350. ID_VALUE(stop_bits)
  351. ID_VALUE(parity)
  352. ID_VALUE(invert)
  353. #undef ID_VALUE
  354. static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROGMEM {
  355. {keys::TxPin, query::tx},
  356. {keys::RxPin, query::rx},
  357. {keys::Baudrate, query::baudrate},
  358. {keys::DataBits, query::data_bits},
  359. {keys::StopBits, query::stop_bits},
  360. {keys::Parity, query::parity},
  361. {keys::Invert, query::invert},
  362. };
  363. bool checkSamePrefix(StringView key) {
  364. PROGMEM_STRING(Prefix, "uart");
  365. return espurna::settings::query::samePrefix(key, Prefix);
  366. }
  367. String findIndexedValueFrom(StringView key) {
  368. return espurna::settings::query::IndexedSetting::findValueFrom(
  369. ports(), IndexedSettings, key);
  370. }
  371. void setup() {
  372. settingsRegisterQueryHandler({
  373. .check = checkSamePrefix,
  374. .get = findIndexedValueFrom,
  375. });
  376. }
  377. } // namespace query
  378. } // namespace settings
  379. #if TERMINAL_SUPPORT
  380. namespace terminal {
  381. namespace commands {
  382. String port_type(Type type) {
  383. const char* out = PSTR("UNKNOWN");
  384. switch (type) {
  385. case Type::Unknown:
  386. break;
  387. case Type::Software:
  388. out = PSTR("SOFTWARE");
  389. break;
  390. case Type::Uart0:
  391. out = PSTR("UART0");
  392. break;
  393. case Type::Uart1:
  394. out = PSTR("UART1");
  395. break;
  396. }
  397. return out;
  398. }
  399. void uart(::terminal::CommandContext&& ctx) {
  400. if (ctx.argv.size() == 1) {
  401. for (size_t index = 0; index < std::size(internal::ports); ++index) {
  402. const auto& port = internal::ports[index];
  403. if (!port) {
  404. break;
  405. }
  406. ctx.output.printf_P(
  407. PSTR("%zu - %s{tx=%c rx=%c}\n"),
  408. index, port_type(port->type).c_str(),
  409. port->tx ? 'y' : 'n',
  410. port->rx ? 'y' : 'n');
  411. }
  412. } else if (ctx.argv.size() == 2) {
  413. const auto result = parseUnsigned(ctx.argv[1], 10);
  414. if (!result.ok) {
  415. terminalError(ctx, F("Invalid ID"));
  416. return;
  417. }
  418. if (result.value >= ports()) {
  419. ctx.output.print(F("(Not active)"));
  420. }
  421. settingsDump(ctx, settings::query::IndexedSettings, result.value);
  422. } else {
  423. terminalError(ctx, F("UART [<ID>]"));
  424. return;
  425. }
  426. terminalOK(ctx);
  427. }
  428. PROGMEM_STRING(Uart, "UART");
  429. static constexpr ::terminal::Command List[] PROGMEM {
  430. {Uart, commands::uart},
  431. };
  432. } // namespace commands
  433. void setup() {
  434. espurna::terminal::add(commands::List);
  435. }
  436. } // namespace terminal
  437. #endif
  438. PortPtr port(size_t index) {
  439. const auto& ptr = internal::ports[index];
  440. if ((index < std::size(internal::ports)) && (ptr)) {
  441. return std::make_unique<Port>(
  442. Port{
  443. .type = ptr->type,
  444. .tx = ptr->tx,
  445. .rx = ptr->rx,
  446. .stream = ptr->stream.get(),
  447. });
  448. }
  449. return nullptr;
  450. }
  451. void setup() {
  452. #if TERMINAL_SUPPORT
  453. terminal::setup();
  454. #endif
  455. settings::query::setup();
  456. for (size_t index = 0; index < build::PortsMax; ++index) {
  457. auto& port = internal::ports[index];
  458. port = make_port(index);
  459. if (!port) {
  460. break;
  461. }
  462. }
  463. }
  464. } // namespace uart
  465. } // namespace
  466. } // namespace driver
  467. } // namespace espurna
  468. espurna::driver::uart::PortPtr uartPort(size_t index) {
  469. return espurna::driver::uart::port(index);
  470. }
  471. void uartSetup() {
  472. espurna::driver::uart::setup();
  473. }
  474. #endif // UART_SUPPORT