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.

2200 lines
59 KiB

2 years ago
2 years ago
2 years ago
6 years ago
2 years ago
6 years ago
6 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
6 years ago
6 years ago
6 years ago
2 years ago
  1. /*
  2. IR MODULE
  3. Copyright (C) 2018 by Alexander Kolesnikov (raw and MQTT implementation)
  4. Copyright (C) 2017-2019 by François Déchery
  5. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  6. Copyright (C) 2020-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  7. For the library, see:
  8. https://github.com/crankyoldgit/IRremoteESP8266
  9. To (re)create the string -> Payload decoder .ipp files, add `re2c` to the $PATH and 'run' the environment:
  10. ```
  11. $ pio run -e ... \
  12. -t espurna/ir_parse_simple.re.ipp \
  13. -t espurna/ir_parse_state.re.ipp \
  14. -t espurna/ir_parse_raw.re.ipp
  15. ```
  16. (see scripts/pio_pre.py and scripts/espurna_utils/build.py for more info)
  17. */
  18. #include "espurna.h"
  19. #if IR_SUPPORT
  20. #include "ir.h"
  21. #include "mqtt.h"
  22. #include "relay.h"
  23. #include "terminal.h"
  24. #include <IRremoteESP8266.h>
  25. #include <IRrecv.h>
  26. #include <IRsend.h>
  27. #include <IRutils.h>
  28. #include <cstdint>
  29. #include <cstring>
  30. #include <queue>
  31. #include <vector>
  32. // TODO: current library version injects a bunch of stuff into the global scope:
  33. // - `__STDC_LIMIT_MACROS`, forcing some c99 macros related to integer limits
  34. // - `enum decode_type_t` with `UNKNOWN` and `UNUSED` symbols in it
  35. // - various `const T k*` defined in the headers (...that are replacing preprocessor tokens :/)
  36. // - various `ENABLE_...`, `SEND_...`, `DECODE_...`, and etc. preprocessor names
  37. // (like `SEND_RAW` and `DECODE_HASH` for internal settings, or `DPRINT` when `DEBUG` is defined)
  38. // ref. IRremoteESP8266.h, IRrecv.h and IRsend.h
  39. //
  40. // One solution is to patch upstream to have an optional `namespace irremoteesp8266 { ... }` wrapping everything related to the lib through a build flag, possibly versioned as well
  41. // And, getting rid of accidentally exported C stuff in favour of C++ alternatives.
  42. namespace ir {
  43. namespace {
  44. namespace tx {
  45. // Notice that IRremoteEsp8266 includes a *lot* of built-in protocols. the suggested way to build the library
  46. // is to append `-D_IR_ENABLE_DEFAULT_=false` to the build flags and specify the individual `-DSEND_...`
  47. // and `-DDECODE_...` *only* for the required one(s)
  48. //
  49. // `-DIR_TX_SUPPORT=0` disables considerable amount of stuff linked inside of the `IRsend` class (~35KiB), but for
  50. // every category of payloads at the same time; simple, raw and state will all be gone. *It is possible* to introduce
  51. // a more granular control, but idk if it's really worth it (...and likely result in an inextricable web of `#if`s and `#ifdef`s)
  52. #if not IR_TX_SUPPORT
  53. struct NoopSender {
  54. NoopSender(uint16_t, bool, bool) {
  55. }
  56. void begin() {
  57. }
  58. bool send(decode_type_t, const uint8_t*, uint16_t) {
  59. return false;
  60. }
  61. bool send(decode_type_t, uint64_t, uint16_t, uint16_t) {
  62. return false;
  63. }
  64. void sendRaw(const uint16_t*, uint16_t, uint16_t) {
  65. }
  66. };
  67. #define IRsend ::ir::tx::NoopSender
  68. #endif
  69. struct PayloadSenderBase {
  70. PayloadSenderBase() = default;
  71. virtual ~PayloadSenderBase() = default;
  72. PayloadSenderBase(const PayloadSenderBase&) = delete;
  73. PayloadSenderBase& operator=(const PayloadSenderBase&) = delete;
  74. PayloadSenderBase(PayloadSenderBase&&) = delete;
  75. PayloadSenderBase& operator=(PayloadSenderBase&&) = delete;
  76. virtual unsigned long delay() const = 0;
  77. virtual bool send(IRsend& sender) const = 0;
  78. virtual bool reschedule() = 0;
  79. };
  80. using PayloadSenderPtr = std::unique_ptr<PayloadSenderBase>;
  81. namespace build {
  82. // pin that the transmitter is attached to
  83. constexpr unsigned char pin() {
  84. return IR_TX_PIN;
  85. }
  86. // (optional) whether the LED will turn ON when GPIO is LOW and OFF when it's HIGH
  87. // (disabled by default)
  88. constexpr bool inverted() {
  89. return IR_TX_INVERTED == 1;
  90. }
  91. // (optional) enable frequency modulation (ref. IRsend.h, enabled by default and assumes 50% duty cycle)
  92. // (usage is 'hidden' by the protocol handlers, which use `::enableIROut(frequency, duty)`)
  93. constexpr bool modulation() {
  94. return IR_TX_MODULATION == 1;
  95. }
  96. // (optional) number of times that the message will be sent immediately
  97. // (i.e. when the [:<repeats>] is omitted from the MQTT payload)
  98. constexpr uint16_t repeats() {
  99. return IR_TX_REPEATS;
  100. }
  101. // (optional) number of times that the message will be scheduled in the TX queue
  102. // (i.e. when the [:<series>] is omitted from the MQTT payload)
  103. constexpr uint8_t series() {
  104. return IR_TX_SERIES;
  105. }
  106. // (ms)
  107. constexpr unsigned long delay() {
  108. return IR_TX_DELAY;
  109. }
  110. } // namespace build
  111. namespace settings {
  112. unsigned char pin() {
  113. return getSetting("irTx", build::pin());
  114. }
  115. bool inverted() {
  116. return getSetting("irTxInv", build::inverted());
  117. }
  118. bool modulation() {
  119. return getSetting("irTxMod", build::modulation());
  120. }
  121. uint16_t repeats() {
  122. return getSetting("rxTxRepeats", build::repeats());
  123. }
  124. uint8_t series() {
  125. return getSetting("rxTxSeries", build::series());
  126. }
  127. unsigned long delay() {
  128. return getSetting("irTxDelay", build::delay());
  129. }
  130. } // namespace settings
  131. namespace internal {
  132. uint16_t repeats { build::repeats() };
  133. uint8_t series { build::series() };
  134. unsigned long delay { build::delay() };
  135. BasePinPtr pin;
  136. std::unique_ptr<IRsend> instance;
  137. std::queue<PayloadSenderPtr> queue;
  138. } // namespace internal
  139. } // namespace tx
  140. namespace rx {
  141. // `-DIR_RX_SUPPORT=0` disables everything related to the `IRrecv` class, ~20KiB
  142. // (note that receiver objects are still techically there, just not doing anything)
  143. #if not IR_RX_SUPPORT
  144. struct NoopReceiver {
  145. NoopReceiver(uint16_t, uint16_t, uint8_t, bool) {
  146. }
  147. bool decode(decode_results*) const {
  148. return false;
  149. }
  150. void disableIRIn() const {
  151. }
  152. void enableIRIn() const {
  153. }
  154. void enableIRIn(bool) const {
  155. }
  156. };
  157. #define IRrecv ::ir::rx::NoopReceiver
  158. #endif
  159. namespace build {
  160. // pin that the receiver is attached to
  161. constexpr unsigned char pin() {
  162. return IR_RX_PIN;
  163. }
  164. // library handles both the isr timer and the pinMode in the same method,
  165. // can't be set externally and must be passed into the `enableIRIn(bool)`
  166. constexpr bool pullup() {
  167. return IR_RX_PULLUP;
  168. }
  169. // internally, lib uses an u16[] of this size
  170. constexpr uint16_t bufferSize() {
  171. return IR_RX_BUFFER_SIZE;
  172. }
  173. // to be isr-friendly, will allocate second u16[]
  174. // that will be used as a storage when decode()'ing
  175. constexpr bool bufferSave() {
  176. return true;
  177. }
  178. // allow unknown protocols to pass through to the processing
  179. // (notice that this *will* cause RAW output to stop working)
  180. constexpr bool unknown() {
  181. return IR_RX_UNKNOWN;
  182. }
  183. // (ms)
  184. constexpr uint8_t timeout() {
  185. return IR_RX_TIMEOUT;
  186. }
  187. // (ms) minimal time in-between decode() calls
  188. constexpr unsigned long delay() {
  189. return IR_RX_DELAY;
  190. }
  191. } // namespace build
  192. namespace settings {
  193. // specific to the IRrecv
  194. unsigned char pin() {
  195. return getSetting("irRx", build::pin());
  196. }
  197. bool pullup() {
  198. return getSetting("irRxPullup", build::pullup());
  199. }
  200. uint16_t bufferSize() {
  201. return getSetting("irRxBufSize", build::bufferSize());
  202. }
  203. uint8_t timeout() {
  204. return getSetting("irRxTimeout", build::timeout());
  205. }
  206. // local settings
  207. bool unknown() {
  208. return getSetting("irRxUnknown", build::unknown());
  209. }
  210. unsigned long delay() {
  211. return getSetting("irRxDelay", build::delay());
  212. }
  213. } // namespace settings
  214. namespace internal {
  215. bool pullup { build::pullup() };
  216. bool unknown { build::unknown() };
  217. unsigned long delay { build::delay() };
  218. BasePinPtr pin;
  219. std::unique_ptr<IRrecv> instance;
  220. } // namespace internal
  221. } // namespace rx
  222. // TODO: some -std=c++11 things. *almost* direct ports of std::string_view and std::optional
  223. // (may be aliased via `using` and depend on the __cplusplus? string view is not 1-to-1 though)
  224. // obviously, missing most of constexpr init and std::optional optimization related to trivial ctors & dtors
  225. //
  226. // TODO: since the exceptions are disabled, and parsing failure is not really an 'exceptional' result anyway...
  227. // result struct may be in need of an additional struct describing the error, instead of just a boolean true or false
  228. // (something like std::expected - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0323r8.html)
  229. // current implementation may be adjusted, but not the using DecodeResult = std::optional<T> mentioned above
  230. struct StringView {
  231. StringView() = delete;
  232. ~StringView() = default;
  233. constexpr StringView(const StringView&) noexcept = default;
  234. constexpr StringView(StringView&&) noexcept = default;
  235. #if __cplusplus > 201103L
  236. constexpr StringView& operator=(const StringView&) noexcept = default;
  237. constexpr StringView& operator=(StringView&&) noexcept = default;
  238. #else
  239. StringView& operator=(const StringView&) noexcept = default;
  240. StringView& operator=(StringView&&) noexcept = default;
  241. #endif
  242. constexpr StringView(std::nullptr_t) noexcept :
  243. _begin(nullptr),
  244. _length(0)
  245. {}
  246. constexpr StringView(const char* begin, size_t length) noexcept :
  247. _begin(begin),
  248. _length(length)
  249. {}
  250. constexpr StringView(const char* begin, const char* end) noexcept :
  251. StringView(begin, end - begin)
  252. {}
  253. template <size_t Size>
  254. constexpr StringView(const char (&string)[Size]) noexcept :
  255. StringView(&string[0], Size - 1)
  256. {}
  257. StringView& operator=(const String& string) noexcept {
  258. _begin = string.c_str();
  259. _length = string.length();
  260. return *this;
  261. }
  262. explicit StringView(const String& string) noexcept :
  263. StringView(string.c_str(), string.length())
  264. {}
  265. template <size_t Size>
  266. constexpr StringView& operator=(const char (&string)[Size]) noexcept {
  267. _begin = &string[0];
  268. _length = Size - 1;
  269. return *this;
  270. }
  271. const char* begin() const noexcept {
  272. return _begin;
  273. }
  274. const char* end() const noexcept {
  275. return _begin + _length;
  276. }
  277. constexpr size_t length() const noexcept {
  278. return _length;
  279. }
  280. explicit operator bool() const {
  281. return _begin && _length;
  282. }
  283. explicit operator String() const {
  284. String out;
  285. out.concat(_begin, _length);
  286. return out;
  287. }
  288. private:
  289. const char* _begin;
  290. size_t _length;
  291. };
  292. template <typename T>
  293. struct ParseResult {
  294. ParseResult() = default;
  295. ParseResult(ParseResult&& other) noexcept {
  296. if (other._initialized) {
  297. init(std::move(other._result._value));
  298. }
  299. }
  300. explicit ParseResult(T&& value) noexcept {
  301. init(std::move(value));
  302. }
  303. ~ParseResult() {
  304. if (_initialized) {
  305. _result._value.~T();
  306. }
  307. }
  308. ParseResult(const ParseResult&) = delete;
  309. ParseResult& operator=(const T& value) = delete;
  310. ParseResult& operator=(T&& value) noexcept {
  311. init(std::move(value));
  312. return *this;
  313. }
  314. explicit operator bool() const noexcept {
  315. return _initialized;
  316. }
  317. T* operator->() {
  318. return &_result._value;
  319. }
  320. const T* operator->() const {
  321. return &_result._value;
  322. }
  323. bool has_value() const noexcept {
  324. return _initialized;
  325. }
  326. const T& value() const & {
  327. return _result._value;
  328. }
  329. T& value() & {
  330. return _result._value;
  331. }
  332. T&& value() && {
  333. return std::move(_result._value);
  334. }
  335. const T&& value() const && {
  336. return std::move(_result._value);
  337. }
  338. private:
  339. struct Empty {
  340. };
  341. union Result {
  342. Result() :
  343. _empty()
  344. {}
  345. ~Result() {
  346. }
  347. Result(const Result&) = delete;
  348. Result(Result&&) = delete;
  349. Result& operator=(const Result&) = delete;
  350. Result& operator=(Result&&) = delete;
  351. template <typename... Args>
  352. Result(Args&&... args) :
  353. _value(std::forward<Args>(args)...)
  354. {}
  355. template <typename... Args>
  356. void update(Args&&... args) {
  357. _value = T(std::forward<Args>(args)...);
  358. }
  359. Empty _empty;
  360. T _value;
  361. };
  362. void reset() {
  363. if (_initialized) {
  364. _result._value.~T();
  365. }
  366. }
  367. // TODO: c++ std compliance may enforce weird optimizations if T contains const or reference members, ref.
  368. // - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0532r0.pdf
  369. // - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
  370. template <typename... Args>
  371. void init(Args&&... args) {
  372. if (!_initialized) {
  373. ::new (&_result) Result(std::forward<Args>(args)...);
  374. _initialized = true;
  375. } else {
  376. _result.update(std::forward<Args>(args)...);
  377. }
  378. }
  379. bool _initialized { false };
  380. Result _result;
  381. };
  382. // TODO: std::from_chars works directly with the view. not available with -std=c++11,
  383. // and needs some care in regards to the code size
  384. template <typename T>
  385. T sized(StringView view) {
  386. String value(view);
  387. char* endp { nullptr };
  388. unsigned long result { std::strtoul(value.c_str(), &endp, 10) };
  389. if ((endp != value.c_str()) && (*endp == '\0')) {
  390. constexpr unsigned long Boundary { 1ul << (sizeof(T) * 8) };
  391. if (result < Boundary) {
  392. return result;
  393. }
  394. }
  395. return 0;
  396. }
  397. template <>
  398. unsigned long sized(StringView view) {
  399. String value(view);
  400. char* endp { nullptr };
  401. unsigned long result { std::strtoul(value.c_str(), &endp, 10) };
  402. if ((endp != value.c_str()) && (*endp == '\0')) {
  403. return result;
  404. }
  405. return 0;
  406. }
  407. // Simple messages that transmit the numeric 'value' (up to 8 bytes)
  408. //
  409. // Transmitting:
  410. // Payload: <protocol>:<value>:<bits>[:<repeats>][:<delay>][:<times>]
  411. //
  412. // Required parameters:
  413. // PROTOCOL - decimal ID, will be converted into a named 'decode_type_t'
  414. // (ref. IRremoteESP8266.h and it's protocol descriptions)
  415. // VALUE - hexadecimal representation of the value that will be sent
  416. // (big endian, maximum 8bytes / 64bit. byte is always zero-padded)
  417. // BITS - number of bits associated with the protocol
  418. // (ref. IRremoteESP8266.h and it's protocol descriptions)
  419. //
  420. // Optional payload parameters:
  421. // REPEATS - how many times the message will be sent immediatly
  422. // (defaults to 0 or the value set by the PROTOCOL type)
  423. // SERIES - how many times the message will be scheduled for sending
  424. // (defaults to 1 aka once, [1...120))
  425. // DELAY - minimum amount of time (ms) between queued messages
  426. // (defaults is IR_TX_DELAY, applies to every message in the series)
  427. //
  428. // Receiving:
  429. // Payload: 2:AABBCCDD:32 (<protocol>:<value>:<bits>)
  430. // TODO: type is numeric based on the previous implementation. note that there are
  431. // `::typeToString(decode_type_t)` and `::strToDecodeType(const char*)` (IRutils.h)
  432. // And also see `const char kAllProtocolNames*`, which is a global from the IRtext header with
  433. // \0-terminated chunks of stringivied decode_type_t (counting 'index' will deduce the type)
  434. //
  435. // (but, notice that str->type only works with C strings and *will* do a permissive
  436. // `strToDecodeType(typeToString(static_cast<decode_type_t>(atoi(str))))` when the
  437. // intial attempt fails)
  438. namespace simple {
  439. struct Payload {
  440. decode_type_t type;
  441. uint64_t value;
  442. uint16_t bits;
  443. uint16_t repeats;
  444. uint8_t series;
  445. unsigned long delay;
  446. };
  447. namespace value {
  448. // TODO: endianness of input is always 'big', output is 'little'
  449. // all esp platforms and common build hosts are 'little'
  450. // but, actually make sure bswap is necessary?
  451. // To convert from an existing decimal value, there is a python one-liner:
  452. // >>> bytes(x for x in (123456789).to_bytes(8, 'big', signed=False) if x).hex()
  453. // '075bcd15'
  454. // (and also notice that old version *always* cast `u64` into `u32` which cut off part of the code)
  455. uint64_t decode(StringView view) {
  456. constexpr size_t RawSize { sizeof(uint64_t) };
  457. constexpr size_t BufferSize { (RawSize * 2) + 1 };
  458. if (!(view.length() % 2) && (view.length() < BufferSize)) {
  459. char buffer[BufferSize] {0};
  460. constexpr size_t BufferLen { BufferSize - 1 };
  461. char* ZerolessOffset { std::begin(buffer) + BufferLen - view.length() };
  462. std::fill(std::begin(buffer), ZerolessOffset, '0');
  463. std::copy(view.begin(), view.end(), ZerolessOffset);
  464. uint8_t raw[RawSize] {0};
  465. if (::hexDecode(buffer, BufferLen, raw, sizeof(raw))) {
  466. uint64_t output{0};
  467. std::memcpy(&output, &raw, sizeof(raw));
  468. return __builtin_bswap64(output);
  469. }
  470. }
  471. return 0;
  472. }
  473. String encode(uint64_t input) {
  474. String out;
  475. if (input) {
  476. const uint64_t Value { __builtin_bswap64(input) };
  477. uint8_t raw[sizeof(Value)] {0};
  478. std::memcpy(&raw, &Value, sizeof(raw));
  479. uint8_t* begin { std::begin(raw) };
  480. while (!(*begin)) {
  481. ++begin;
  482. }
  483. out = hexEncode(begin, std::end(raw));
  484. } else {
  485. out.concat(F("00"));
  486. }
  487. return out;
  488. }
  489. } // namespace value
  490. namespace payload {
  491. decode_type_t type(StringView view) {
  492. static_assert(std::is_same<int, std::underlying_type<decode_type_t>::type>::value, "");
  493. constexpr int First { -1 };
  494. constexpr int Last { static_cast<int>(decode_type_t::kLastDecodeType) };
  495. String value(view);
  496. int result { value.toInt() };
  497. if ((First < result) && (result < Last)) {
  498. return static_cast<decode_type_t>(result);
  499. }
  500. return decode_type_t::UNKNOWN;
  501. }
  502. uint64_t value(StringView view) {
  503. return ::ir::simple::value::decode(view);
  504. }
  505. uint16_t bits(StringView value) {
  506. return sized<uint16_t>(value);
  507. }
  508. uint16_t repeats(StringView value) {
  509. return sized<uint16_t>(value);
  510. }
  511. uint8_t series(StringView value) {
  512. return sized<uint8_t>(value);
  513. }
  514. unsigned long delay(StringView value) {
  515. return sized<unsigned long>(value);
  516. }
  517. template <typename T>
  518. String encode(T& result) {
  519. String out;
  520. out.reserve(28);
  521. out += static_cast<int>(result.type());
  522. out += ':';
  523. out += value::encode(result.value());
  524. out += ':';
  525. out += static_cast<unsigned int>(result.bits());
  526. return out;
  527. }
  528. } // namespace payload
  529. Payload prepare(StringView type, StringView value, StringView bits, StringView repeats, StringView series, StringView delay) {
  530. Payload result;
  531. result.type = payload::type(type);
  532. result.value = payload::value(value);
  533. result.bits = payload::bits(bits);
  534. if (repeats) {
  535. result.repeats = payload::repeats(repeats);
  536. } else {
  537. result.repeats = tx::internal::repeats;
  538. }
  539. if (series) {
  540. result.series = payload::series(series);
  541. } else {
  542. result.series = tx::internal::series;
  543. }
  544. if (delay) {
  545. result.delay = payload::delay(delay);
  546. } else {
  547. result.delay = tx::internal::delay;
  548. }
  549. return result;
  550. }
  551. #include "ir_parse_simple.re.ipp"
  552. } // namespace simple
  553. // Transmitting:
  554. // Payload: <frequency>:<series>:<delay>:<μs>,<μs>,<μs>,<μs>,...
  555. // | Options | | Message |
  556. //
  557. // FREQUENCY - modulation frequency, either in kHz (<1000) or Hz (>=1000)
  558. // SERIES - how many times the message will be scheduled for sending
  559. // [1...120)
  560. // DELAY - minimum amount of time (ms) between queued messages
  561. //
  562. // Receiving:
  563. // Payload: <μs>,<μs>,<μs>,<μs>,...
  564. //
  565. // The message is encoded as time in microseconds for the IR LED to be in a certain state.
  566. // First one is always ON, and the second one - OFF.
  567. // Also see IRutils.h's `String resultToTimingInfo(decode_results*)` for all of timing info, with a nice table output
  568. // Not really applicable here, though
  569. namespace raw {
  570. static_assert((DECODE_HASH), "");
  571. struct Payload {
  572. uint16_t frequency;
  573. uint8_t series;
  574. unsigned long delay;
  575. std::vector<uint16_t> time;
  576. };
  577. namespace time {
  578. // TODO: compress / decompress with https://tasmota.github.io/docs/IRSend-RAW-Encoding/?
  579. //
  580. // Each rawbuf TIME value is:
  581. // - multiplied by the TICK (old RAWTICK, currently kRawTick in a *global scope*)
  582. // - rounded to the closest multiple of 5 (e.g. 299 becomes 300)
  583. // - assigned an english alphabet letter ID (...or not, when exhausted all of 26 letters)
  584. // Resulting payload contains TIME(μs) alternating between ON and OFF, starting with ON:
  585. // - when first seen, output time directly prefixed with either '+' (ON) or '-' (OFF)
  586. // - on further appearences, replace the time value with a letter that is uppercase for ON and lowercase for OFF
  587. //
  588. // For example, current implementation:
  589. // > 100,200,100,200,200,300,300,300
  590. // |A| |B| |A| |B| |B| |C| |C| |C|
  591. // Becomes:
  592. // > +100-200AbB-300Cc
  593. // |A| |B| |C|
  594. String encode(const uint16_t* begin, const uint16_t* end) {
  595. static_assert((kRawTick == 2), "");
  596. String out;
  597. out.reserve((end - begin) * 5);
  598. for (const uint16_t* it = begin; it != end; ++it) {
  599. if (out.length()) {
  600. out += ',';
  601. }
  602. out += String((*it) * kRawTick, 10);
  603. }
  604. return out;
  605. }
  606. } // namespace time
  607. namespace payload {
  608. uint16_t frequency(StringView value) {
  609. return sized<uint16_t>(value);
  610. }
  611. uint8_t series(StringView value) {
  612. return sized<uint8_t>(value);
  613. }
  614. unsigned long delay(StringView value) {
  615. return sized<unsigned long>(value);
  616. }
  617. uint16_t time(StringView value) {
  618. return sized<uint16_t>(value);
  619. }
  620. template <typename T>
  621. String encode(T& result) {
  622. auto raw = result.raw();
  623. if (raw) {
  624. return time::encode(raw.begin(), raw.end());
  625. }
  626. return F("0");
  627. }
  628. } // namespace payload
  629. Payload prepare(StringView frequency, StringView series, StringView delay, decltype(Payload::time)&& time) {
  630. Payload result;
  631. result.frequency = payload::frequency(frequency);
  632. result.series = payload::series(series);
  633. result.delay = payload::delay(delay);
  634. result.time = std::move(time);
  635. return result;
  636. }
  637. #include "ir_parse_raw.re.ipp"
  638. } // namespace raw
  639. // TODO: current solution works directly with the internal 'u8 state[]', both for receiving and sending
  640. // a more complex protocols for HVAC equipment *could* be handled by the IRacUtils (ref. IRac.h)
  641. // where a generic 'IRac' class will convert certain common properties like temperature, fan speed,
  642. // fan direction and power toggle (and some more, see 'stdAc::state_t'; or, the specific vendor class)
  643. //
  644. // Some problems with state_t, though:
  645. // - not everything is 1-to-1 convertible with specific-protocol-AC-class to state_t
  646. // (or not directly, or with some unexpected limitations)
  647. // - there's no apparent way to know which properties are supported by the protocol.
  648. // protocol-specific classes (e.g. MitsubishiAC) will convert to state_t by omitting certain fields,
  649. // and parse it by ignoring them. but, this is hidden in the implementation
  650. // - some protocols require previous state as a reference for sending, and IRac already has an internal copy
  651. // if the state_t struct. but, notice that it is shared between protocols (as a generic way), so mixing
  652. // protocols becomes are bit of a headache
  653. // - size of the payload is as wide as the largest one, so there's always a static blob of N
  654. // bytes reserved, both inside and with the proposed API of the library
  655. // saving state (to avoid always resetting to defaults on reboot) also becomes a problem,
  656. //
  657. // For a generic solution, supporting state_t would mean to allow to set *every* property declared by the struct
  658. // Common examples and libraries wrapping IRac prefer JSON payload, and both IRac and IRutils contain helpers to convert
  659. // each property to and from strings.
  660. //
  661. // But, preper to split HVAC into a different module, as none of the queueing or generic code facilities are actually useful.
  662. namespace state {
  663. // State messages transmit an arbitrary amount of bytes, by using the assosicated protocol method
  664. // Repeats are intended to be handled via the respective PROTOCOL method automatically
  665. // (and, there's no reliable way besides linking every type with it's method from our side)
  666. //
  667. // Transmitting:
  668. // Payload: <protocol>:<value>[:<series>][:<delay>]
  669. //
  670. // Required parameters:
  671. // PROTOCOL - decimal ID, will be converted into a named 'decode_type_t'
  672. // (ref. IRremoteESP8266.h and it's protocol descriptions)
  673. // VALUE - hexadecimal representation of the value that will be sent
  674. // (big endian, maximum depends on the protocol settings)
  675. //
  676. // Optional payload parameters:
  677. // SERIES - how many times the message will be scheduled for sending
  678. // (defaults to 1 aka once, [1...120))
  679. // DELAY - minimum amount of time (ms) between queued messages
  680. // (defaults is IR_TX_DELAY, applies to every message in the series)
  681. //
  682. // Receiving:
  683. // Payload: 52:112233445566778899AABB (<protocol>:<value>)
  684. static_assert(
  685. sizeof(decltype(decode_results::state)) >= sizeof(decltype(decode_results::value)),
  686. "Unsupported version of IRremoteESP8266");
  687. using Value = std::vector<uint8_t>;
  688. struct Payload {
  689. decode_type_t type;
  690. Value value;
  691. uint8_t series;
  692. unsigned long delay;
  693. };
  694. namespace value {
  695. String encode(const uint8_t* begin, const uint8_t* end) {
  696. return hexEncode(begin, end);
  697. }
  698. template <typename T>
  699. String encode(T&& range) {
  700. return hexEncode(range.begin(), range.end());
  701. }
  702. Value decode(StringView view) {
  703. Value out;
  704. if (!(view.length() % 2)) {
  705. out.resize(view.length() / 2, static_cast<uint8_t>(0));
  706. hexDecode(view.begin(), view.end(),
  707. out.data(), out.data() + out.size());
  708. }
  709. return out;
  710. }
  711. } // namespace value
  712. namespace payload {
  713. template <typename T>
  714. String encode(T& result) {
  715. String out;
  716. out.reserve(4 + (result.bytes() * 2));
  717. out += static_cast<int>(result.type());
  718. out += ':';
  719. auto state = result.state();
  720. out += value::encode(state.begin(), state.end());
  721. return out;
  722. }
  723. } // namespace payload
  724. Payload prepare(StringView type, StringView value, StringView series, StringView delay) {
  725. Payload result;
  726. result.type = ::ir::simple::payload::type(type);
  727. result.value = value::decode(value);
  728. if (series) {
  729. result.series = simple::payload::series(series);
  730. } else {
  731. result.series = tx::internal::series;
  732. }
  733. if (delay) {
  734. result.delay = simple::payload::delay(delay);
  735. } else {
  736. result.delay = tx::internal::delay;
  737. }
  738. return result;
  739. }
  740. #include "ir_parse_state.re.ipp"
  741. } // namespace state
  742. namespace rx {
  743. struct Lock {
  744. Lock(const Lock&) = delete;
  745. Lock(Lock&&) = delete;
  746. Lock& operator=(const Lock&) = delete;
  747. Lock& operator=(Lock&&) = delete;
  748. Lock() {
  749. if (internal::instance) {
  750. internal::instance->disableIRIn();
  751. }
  752. }
  753. ~Lock() {
  754. if (internal::instance) {
  755. internal::instance->enableIRIn(internal::pullup);
  756. }
  757. }
  758. };
  759. void configure() {
  760. internal::delay = settings::delay();
  761. internal::pullup = settings::pullup();
  762. internal::unknown = settings::unknown();
  763. }
  764. void setup(BasePinPtr&& pin) {
  765. internal::pin = std::move(pin);
  766. internal::instance = std::make_unique<IRrecv>(
  767. internal::pin->pin(),
  768. settings::bufferSize(),
  769. settings::timeout(),
  770. build::bufferSave());
  771. internal::instance->enableIRIn(internal::pullup);
  772. }
  773. // Current implementation relies on the HEX-encoded 'value' (previously, decimal)
  774. //
  775. // XXX: when protocol is UNKNOWN, `value` is silently replaced with a fnv1 32bit hash.
  776. // can be disabled with `-DDECODE_HASH=0` in the build flags, but it will also
  777. // cause RAW output to stop working, as the `IRrecv::decode()` can never succeed :/
  778. //
  779. // XXX: library utilizes union as a way to store the data, making this an interesting case
  780. // of two-way aliasing inside of the struct. (and sometimes unexpected size requirements)
  781. //
  782. // At the time of writing, it is:
  783. // > union {
  784. // > struct {
  785. // > uint64_t value; // Decoded value
  786. // > uint32_t address; // Decoded device address.
  787. // > uint32_t command; // Decoded command.
  788. // > };
  789. // > uint8_t state[kStateSizeMax]; // Multi-byte results.
  790. // > };
  791. //
  792. // Where `kStateSizeMax` is either:
  793. // - deduced from the largest protocol from the `DECODE_AC` group, *if* any of the protocols is enabled
  794. // - otherwise, it's just `sizeof(uint64_t)`
  795. // (i.e. only extra data is lost, as union members always start at the beginning of the struct)
  796. // Also see IRutils.h's `String resultToHumanReadableBasic(decode_results*);` for type + value as a single line
  797. struct DecodeResult {
  798. template <typename T>
  799. struct Range {
  800. Range() = default;
  801. Range(const T* begin, const T* end) :
  802. _begin(begin),
  803. _end(end)
  804. {}
  805. const T* begin() const {
  806. return _begin;
  807. }
  808. const T* end() const {
  809. return _end;
  810. }
  811. explicit operator bool() const {
  812. return _begin && _end && (_begin < _end);
  813. }
  814. private:
  815. const T* _begin { nullptr };
  816. const T* _end { nullptr };
  817. };
  818. DecodeResult() = delete;
  819. explicit DecodeResult(::decode_results& result) :
  820. _result(result)
  821. {}
  822. decode_type_t type() const {
  823. return _result.decode_type;
  824. }
  825. explicit operator bool() const {
  826. return type() != decode_type_t::UNKNOWN;
  827. }
  828. uint16_t bits() const {
  829. return _result.bits;
  830. }
  831. uint64_t value() const {
  832. return _result.value;
  833. }
  834. // TODO: library examples (and some internal code, too) prefer this to be `bits() / 8`
  835. size_t bytes() const {
  836. const size_t Bits { bits() };
  837. size_t need { 0 };
  838. size_t out { 0 };
  839. while (need < Bits) {
  840. need += 8u;
  841. out += 1u;
  842. }
  843. return out;
  844. }
  845. using Raw = Range<uint16_t>;
  846. Raw raw() const {
  847. if (_result.rawlen > 1) {
  848. return Raw{
  849. const_cast<const uint16_t*>(&_result.rawbuf[1]),
  850. const_cast<const uint16_t*>(&_result.rawbuf[_result.rawlen])};
  851. }
  852. return {};
  853. }
  854. using State = Range<uint8_t>;
  855. State state() const {
  856. const size_t End { std::min(bytes(), sizeof(decltype(_result.state))) };
  857. return State{
  858. &_result.state[0],
  859. &_result.state[End]};
  860. }
  861. private:
  862. const ::decode_results& _result;
  863. };
  864. } // namespace rx
  865. namespace tx {
  866. // TODO: variant instead of virtuals?
  867. struct ReschedulablePayload : public PayloadSenderBase {
  868. static constexpr uint8_t SeriesMax { 120 };
  869. ReschedulablePayload() = delete;
  870. ~ReschedulablePayload() = default;
  871. ReschedulablePayload(const ReschedulablePayload&) = delete;
  872. ReschedulablePayload& operator=(const ReschedulablePayload&) = delete;
  873. ReschedulablePayload(ReschedulablePayload&&) = delete;
  874. ReschedulablePayload& operator=(ReschedulablePayload&&) = delete;
  875. ReschedulablePayload(uint8_t series, unsigned long delay) :
  876. _series(std::min(series, SeriesMax)),
  877. _delay(delay)
  878. {}
  879. bool reschedule() override {
  880. return _series && (--_series);
  881. }
  882. unsigned long delay() const override {
  883. return _delay;
  884. }
  885. protected:
  886. size_t series() const {
  887. return _series;
  888. }
  889. private:
  890. uint8_t _series;
  891. unsigned long _delay;
  892. };
  893. struct SimplePayloadSender : public ReschedulablePayload {
  894. SimplePayloadSender() = delete;
  895. explicit SimplePayloadSender(ir::simple::Payload&& payload) :
  896. ReschedulablePayload(payload.series, payload.delay),
  897. _payload(std::move(payload))
  898. {}
  899. bool send(IRsend& sender) const override {
  900. return series() && sender.send(_payload.type, _payload.value, _payload.bits, _payload.repeats);
  901. }
  902. private:
  903. ir::simple::Payload _payload;
  904. };
  905. struct StatePayloadSender : public ReschedulablePayload {
  906. StatePayloadSender() = delete;
  907. explicit StatePayloadSender(ir::state::Payload&& payload) :
  908. ReschedulablePayload(
  909. (payload.value.size() ? payload.series : 0), payload.delay),
  910. _payload(std::move(payload))
  911. {}
  912. bool send(IRsend& sender) const override {
  913. return series() && sender.send(_payload.type, _payload.value.data(), _payload.value.size());
  914. }
  915. private:
  916. ir::state::Payload _payload;
  917. };
  918. struct RawPayloadSender : public ReschedulablePayload {
  919. RawPayloadSender() = delete;
  920. explicit RawPayloadSender(ir::raw::Payload&& payload) :
  921. ReschedulablePayload(
  922. (payload.time.size() ? payload.series : 0), payload.delay),
  923. _payload(std::move(payload))
  924. {}
  925. bool send(IRsend& sender) const override {
  926. if (series()) {
  927. sender.sendRaw(_payload.time.data(), _payload.time.size(), _payload.frequency);
  928. return true;
  929. }
  930. return false;
  931. }
  932. private:
  933. ir::raw::Payload _payload;
  934. };
  935. namespace internal {
  936. PayloadSenderPtr make_sender(ir::simple::Payload&& payload) {
  937. return std::make_unique<SimplePayloadSender>(std::move(payload));
  938. }
  939. PayloadSenderPtr make_sender(ir::state::Payload&& payload) {
  940. return std::make_unique<StatePayloadSender>(std::move(payload));
  941. }
  942. PayloadSenderPtr make_sender(ir::raw::Payload&& payload) {
  943. return std::make_unique<RawPayloadSender>(std::move(payload));
  944. }
  945. void enqueue(PayloadSenderPtr&& sender) {
  946. queue.push(std::move(sender));
  947. }
  948. } // namespace internal
  949. template <typename T>
  950. bool enqueue(typename ir::ParseResult<T>&& result) {
  951. if (result) {
  952. internal::enqueue(internal::make_sender(std::move(result).value()));
  953. return true;
  954. }
  955. return false;
  956. }
  957. void loop() {
  958. if (internal::queue.empty()) {
  959. return;
  960. }
  961. auto& payload = internal::queue.front();
  962. static unsigned long last { millis() - payload->delay() - 1ul };
  963. const unsigned long timestamp { millis() };
  964. if (timestamp - last < payload->delay()) {
  965. return;
  966. }
  967. last = timestamp;
  968. rx::Lock lock;
  969. if (!payload->send(*internal::instance)) {
  970. internal::queue.pop();
  971. return;
  972. }
  973. if (!payload->reschedule()) {
  974. internal::queue.pop();
  975. }
  976. }
  977. void configure() {
  978. internal::delay = settings::delay();
  979. internal::series = settings::series();
  980. internal::repeats = settings::repeats();
  981. }
  982. void setup(BasePinPtr&& pin) {
  983. internal::pin = std::move(pin);
  984. internal::instance = std::make_unique<IRsend>(
  985. internal::pin->pin(),
  986. settings::inverted(),
  987. settings::modulation());
  988. internal::instance->begin();
  989. }
  990. } // namespace tx
  991. #if MQTT_SUPPORT
  992. namespace mqtt {
  993. namespace build {
  994. // (optional) enables simple protocol MQTT rx output
  995. constexpr bool rxSimple() {
  996. return IR_RX_SIMPLE_MQTT == 1;
  997. }
  998. // (optional) enables MQTT RAW rx output (i.e. time values that we received so far)
  999. constexpr bool rxRaw() {
  1000. return IR_RX_RAW_MQTT == 1;
  1001. }
  1002. // (optional) enables MQTT state rx output (commonly, HVAC remotes, or anything that has payload larger than 64bit)
  1003. // (*may need* increased timeout setting for the receiver, so it could buffer very large messages consistently and not lose some of the parts)
  1004. // (*requires* increase buffer size. but, depends on the protocol, so adjust accordingly)
  1005. constexpr bool rxState() {
  1006. return IR_RX_STATE_MQTT == 1;
  1007. }
  1008. // {root}/{topic}
  1009. const char* topicRxSimple() {
  1010. return IR_RX_SIMPLE_MQTT_TOPIC;
  1011. }
  1012. const char* topicTxSimple() {
  1013. return IR_TX_SIMPLE_MQTT_TOPIC;
  1014. }
  1015. const char* topicRxRaw() {
  1016. return IR_RX_RAW_MQTT_TOPIC;
  1017. }
  1018. const char* topicTxRaw() {
  1019. return IR_TX_RAW_MQTT_TOPIC;
  1020. }
  1021. const char* topicRxState() {
  1022. return IR_RX_STATE_MQTT_TOPIC;
  1023. }
  1024. const char* topicTxState() {
  1025. return IR_TX_STATE_MQTT_TOPIC;
  1026. }
  1027. } // namespace build
  1028. namespace settings {
  1029. bool rxSimple() {
  1030. return getSetting("irRxMqtt", build::rxSimple());
  1031. }
  1032. bool rxRaw() {
  1033. return getSetting("irRxMqttRaw", build::rxRaw());
  1034. }
  1035. bool rxState() {
  1036. return getSetting("irRxMqttState", build::rxState());
  1037. }
  1038. } // namespace settings
  1039. namespace internal {
  1040. bool publish_raw { build::rxRaw() };
  1041. bool publish_simple { build::rxSimple() };
  1042. bool publish_state { build::rxState() };
  1043. void callback(unsigned int type, const char* topic, char* payload) {
  1044. switch (type) {
  1045. case MQTT_CONNECT_EVENT:
  1046. mqttSubscribe(build::topicTxSimple());
  1047. mqttSubscribe(build::topicTxState());
  1048. mqttSubscribe(build::topicTxRaw());
  1049. break;
  1050. case MQTT_MESSAGE_EVENT: {
  1051. StringView view{payload, payload + strlen(payload)};
  1052. String t = mqttMagnitude(topic);
  1053. if (t.equals(build::topicTxSimple())) {
  1054. ir::tx::enqueue(ir::simple::parse(view));
  1055. } else if (t.equals(build::topicTxState())) {
  1056. ir::tx::enqueue(ir::state::parse(view));
  1057. } else if (t.equals(build::topicTxRaw())) {
  1058. ir::tx::enqueue(ir::raw::parse(view));
  1059. }
  1060. break;
  1061. }
  1062. }
  1063. }
  1064. } // namespace internal
  1065. void process(rx::DecodeResult& result) {
  1066. if (internal::publish_state && result && (result.bytes() > 8)) {
  1067. ::mqttSend(build::topicRxState(), ::ir::state::payload::encode(result).c_str());
  1068. } else if (internal::publish_simple) {
  1069. ::mqttSend(build::topicRxSimple(), ::ir::simple::payload::encode(result).c_str());
  1070. }
  1071. if (internal::publish_raw) {
  1072. ::mqttSend(build::topicRxRaw(), ::ir::raw::payload::encode(result).c_str());
  1073. }
  1074. }
  1075. void configure() {
  1076. internal::publish_raw = settings::rxRaw();
  1077. internal::publish_simple = settings::rxSimple();
  1078. internal::publish_state = settings::rxState();
  1079. }
  1080. void setup() {
  1081. mqttRegister(internal::callback);
  1082. }
  1083. } // namespace mqtt
  1084. #endif
  1085. #if TERMINAL_SUPPORT
  1086. namespace terminal {
  1087. struct ValueCommand {
  1088. const __FlashStringHelper* value;
  1089. const __FlashStringHelper* command;
  1090. };
  1091. struct Preset {
  1092. const ValueCommand* const begin;
  1093. const ValueCommand* const end;
  1094. };
  1095. namespace build {
  1096. // TODO: optimize the array itself via PROGMEM? can't be static though, b/c F(...) will be resolved later and the memory is empty in the flash
  1097. // also note of the alignment requirements that don't always get applied to a simple PROGMEM'ed array (unless explicitly set, or the contained value is aligned)
  1098. // strings vs. number for values do have a slight overhead (x2 pointers, byte-by-byte cmp instead of a 2byte memcmp), but it seems to be easier to handle here
  1099. // but... this also means it *could* seamlessly handle state payloads just as simple values, just by changing the value retrieval function
  1100. // TODO: have an actual name for presets (remote, device, etc.)?
  1101. // TODO: user-defined presets?
  1102. // TODO: pub-sub through terminal?
  1103. // Replaced old ir_button.h IR_BUTTON_ACTION_... with an appropriate terminal command
  1104. // Unlike the RFbridge implementation, does not depend on the RELAY_SUPPORT and it's indexes
  1105. #if IR_RX_PRESET != 0
  1106. Preset preset() {
  1107. #if IR_RX_PRESET == 1
  1108. // For the original Remote shipped with the controller
  1109. // +------+------+------+------+
  1110. // | UP | Down | OFF | ON |
  1111. // +------+------+------+------+
  1112. // | R | G | B | W |
  1113. // +------+------+------+------+
  1114. // | 1 | 2 | 3 |FLASH |
  1115. // +------+------+------+------+
  1116. // | 4 | 5 | 6 |STROBE|
  1117. // +------+------+------+------+
  1118. // | 7 | 8 | 9 | FADE |
  1119. // +------+------+------+------+
  1120. // | 10 | 11 | 12 |SMOOTH|
  1121. // +------+------+------+------+
  1122. static const std::array<ValueCommand, 20> instance {
  1123. {{F("FF906F"), F("brightness +10")},
  1124. {F("FFB847"), F("brightness -10")},
  1125. {F("FFF807"), F("light off")},
  1126. {F("FFB04F"), F("light on")},
  1127. {F("FF9867"), F("rgb #FF0000")},
  1128. {F("FFD827"), F("rgb #00FF00")},
  1129. {F("FF8877"), F("rgb #0000FF")},
  1130. {F("FFA857"), F("rgb #FFFFFF")},
  1131. {F("FFE817"), F("rgb #D13A01")},
  1132. {F("FF48B7"), F("rgb #00E644")},
  1133. {F("FF6897"), F("rgb #0040A7")},
  1134. //{F("FFB24D"), F("effect flash")},
  1135. {F("FF02FD"), F("rgb #E96F2A")},
  1136. {F("FF32CD"), F("rgb #00BEBF")},
  1137. {F("FF20DF"), F("rgb #56406F")},
  1138. //{F("FF00FF"), F("effect strobe")},
  1139. {F("FF50AF"), F("rgb #EE9819")},
  1140. {F("FF7887"), F("rgb #00799A")},
  1141. {F("FF708F"), F("rgb #944E80")},
  1142. //{F("FF58A7"), F("effect fade")},
  1143. {F("FF38C7"), F("rgb #FFFF00")},
  1144. {F("FF28D7"), F("rgb #0060A1")},
  1145. {F("FFF00F"), F("rgb #EF45AD")}}
  1146. //{F("FF30CF"), F("effect smooth")}
  1147. };
  1148. #elif IR_RX_PRESET == 2
  1149. // Another identical IR Remote shipped with another controller
  1150. // +------+------+------+------+
  1151. // | UP | Down | OFF | ON |
  1152. // +------+------+------+------+
  1153. // | R | G | B | W |
  1154. // +------+------+------+------+
  1155. // | 1 | 2 | 3 |FLASH |
  1156. // +------+------+------+------+
  1157. // | 4 | 5 | 6 |STROBE|
  1158. // +------+------+------+------+
  1159. // | 7 | 8 | 9 | FADE |
  1160. // +------+------+------+------+
  1161. // | 10 | 11 | 12 |SMOOTH|
  1162. // +------+------+------+------+
  1163. static const std::array<ValueCommand, 20> instance {
  1164. {{F("FF00FF"), F("brightness +10")},
  1165. {F("FF807F"), F("brightness -10")},
  1166. {F("FF40BF"), F("light off")},
  1167. {F("FFC03F"), F("light on")},
  1168. {F("FF20DF"), F("rgb #FF0000")},
  1169. {F("FFA05F"), F("rgb #00FF00")},
  1170. {F("FF609F"), F("rgb #0000FF")},
  1171. {F("FFE01F"), F("rgb #FFFFFF")},
  1172. {F("FF10EF"), F("rgb #D13A01")},
  1173. {F("FF906F"), F("rgb #00E644")},
  1174. {F("FF50AF"), F("rgb #0040A7")},
  1175. //{F("FFD02F"), F("effect flash")},
  1176. {F("FF30CF"), F("rgb #E96F2A")},
  1177. {F("FFB04F"), F("rgb #00BEBF")},
  1178. {F("FF708F"), F("rgb #56406F")},
  1179. //{F("FFF00F"), F("effect strobe")},
  1180. {F("FF08F7"), F("rgb #EE9819")},
  1181. {F("FF8877"), F("rgb #00799A")},
  1182. {F("FF48B7"), F("rgb #944E80")},
  1183. //{F("FFC837"), F("effect fade")},
  1184. {F("FF28D7"), F("rgb #FFFF00")},
  1185. {F("FFA857"), F("rgb #0060A1")},
  1186. {F("FF6897"), F("rgb #EF45AD")}}
  1187. //{F("FFE817"), F("effect smooth")}
  1188. };
  1189. #elif IR_RX_PRESET == 3
  1190. // Samsung AA59-00608A for a generic 8CH module
  1191. // +------+------+------+
  1192. // | 1 | 2 | 3 |
  1193. // +------+------+------+
  1194. // | 4 | 5 | 6 |
  1195. // +------+------+------+
  1196. // | 7 | 8 | 9 |
  1197. // +------+------+------+
  1198. // | | 0 | |
  1199. // +------+------+------+
  1200. static const std::array<ValueCommand, 8> instance {
  1201. {{F("E0E020DF"), F("relay 0 toggle")},
  1202. {F("E0E0A05F"), F("relay 1 toggle")},
  1203. {F("E0E0609F"), F("relay 2 toggle")},
  1204. {F("E0E010EF"), F("relay 3 toggle")},
  1205. {F("E0E0906F"), F("relay 4 toggle")},
  1206. {F("E0E050AF"), F("relay 5 toggle")},
  1207. {F("E0E030CF"), F("relay 6 toggle")},
  1208. {F("E0E0B04F"), F("relay 7 toggle")}}
  1209. };
  1210. // Plus, 2 extra buttons (TODO: on each side of 0?)
  1211. // - E0E0708F
  1212. // - E0E08877
  1213. #elif IR_RX_PRESET == 4
  1214. // +------+------+------+
  1215. // | OFF | SRC | MUTE |
  1216. // +------+------+------+
  1217. // ...
  1218. // +------+------+------+
  1219. // TODO: ...but what's the rest?
  1220. static const std::array<ValueCommand, 1> instance {
  1221. {F("FFB24D"), F("relay 0 toggle")}
  1222. };
  1223. #elif IR_RX_PRESET == 5
  1224. // Another identical IR Remote shipped with another controller as SET 1 and 2
  1225. // +------+------+------+------+
  1226. // | UP | Down | OFF | ON |
  1227. // +------+------+------+------+
  1228. // | R | G | B | W |
  1229. // +------+------+------+------+
  1230. // | 1 | 2 | 3 |FLASH |
  1231. // +------+------+------+------+
  1232. // | 4 | 5 | 6 |STROBE|
  1233. // +------+------+------+------+
  1234. // | 7 | 8 | 9 | FADE |
  1235. // +------+------+------+------+
  1236. // | 10 | 11 | 12 |SMOOTH|
  1237. // +------+------+------+------+
  1238. static const std::array<ValueCommand, 20> instance {
  1239. {{F("F700FF"), F("brightness +10")},
  1240. {F("F7807F"), F("brightness -10")},
  1241. {F("F740BF"), F("light off")},
  1242. {F("F7C03F"), F("light on")},
  1243. {F("F720DF"), F("rgb #FF0000")},
  1244. {F("F7A05F"), F("rgb #00FF00")},
  1245. {F("F7609F"), F("rgb #0000FF")},
  1246. {F("F7E01F"), F("rgb #FFFFFF")},
  1247. {F("F710EF"), F("rgb #D13A01")},
  1248. {F("F7906F"), F("rgb #00E644")},
  1249. {F("F750AF"), F("rgb #0040A7")},
  1250. //{F("F7D02F"), F("effect flash")},
  1251. {F("F730CF"), F("rgb #E96F2A")},
  1252. {F("F7B04F"), F("rgb #00BEBF")},
  1253. {F("F7708F"), F("rgb #56406F")},
  1254. //{F("F7F00F"), F("effect strobe")},
  1255. {F("F708F7"), F("rgb #EE9819")},
  1256. {F("F78877"), F("rgb #00799A")},
  1257. {F("F748B7"), F("rgb #944E80")},
  1258. //{F("F7C837"), F("effect fade")},
  1259. {F("F728D7"), F("rgb #FFFF00")},
  1260. {F("F7A857"), F("rgb #0060A1")},
  1261. {F("F76897"), F("rgb #EF45AD")}}
  1262. //{F("F7E817"), F("effect smooth")}
  1263. };
  1264. #else
  1265. #error "Preset is not handled"
  1266. #endif
  1267. return {std::begin(instance), std::end(instance)};
  1268. }
  1269. #endif
  1270. } // namespace build
  1271. namespace internal {
  1272. void inject(String command) {
  1273. terminalInject(command.c_str(), command.length());
  1274. if (!command.endsWith("\r\n") && !command.endsWith("\n")) {
  1275. terminalInject('\n');
  1276. }
  1277. }
  1278. } // namespace internal
  1279. void process(rx::DecodeResult& result) {
  1280. auto value = ir::simple::value::encode(result.value());
  1281. #if IR_RX_PRESET != 0
  1282. auto preset = build::preset();
  1283. if (preset.begin && preset.end && (preset.begin != preset.end)) {
  1284. for (auto* it = preset.begin; it != preset.end; ++it) {
  1285. String other((*it).value);
  1286. if (other == value) {
  1287. internal::inject((*it).command);
  1288. return;
  1289. }
  1290. }
  1291. }
  1292. #endif
  1293. String key;
  1294. key += F("irCmd");
  1295. key += value;
  1296. auto cmd = ::settings::internal::get(key);
  1297. if (cmd) {
  1298. internal::inject(cmd.ref());
  1299. }
  1300. }
  1301. void setup() {
  1302. terminalRegisterCommand(F("IR.SEND"), [](const ::terminal::CommandContext& ctx) {
  1303. if (ctx.argv.size() == 2) {
  1304. auto view = StringView{ctx.argv[1]};
  1305. auto simple = ir::simple::parse(view);
  1306. if (ir::tx::enqueue(std::move(simple))) {
  1307. terminalOK(ctx);
  1308. return;
  1309. }
  1310. auto state = ir::state::parse(view);
  1311. if (ir::tx::enqueue(std::move(state))) {
  1312. terminalOK(ctx);
  1313. return;
  1314. }
  1315. auto raw = ir::raw::parse(view);
  1316. if (ir::tx::enqueue(std::move(raw))) {
  1317. terminalOK(ctx);
  1318. return;
  1319. }
  1320. terminalError(ctx, F("Invalid payload"));
  1321. return;
  1322. }
  1323. terminalError(ctx, F("IR.SEND <PAYLOAD>"));
  1324. });
  1325. }
  1326. } // namespace terminal
  1327. #endif
  1328. #if DEBUG_SUPPORT
  1329. namespace debug {
  1330. void log(rx::DecodeResult& result) {
  1331. if (!result) {
  1332. DEBUG_MSG_P(PSTR("[IR] IN unknown value %s\n"),
  1333. ir::simple::value::encode(result.value()).c_str());
  1334. } else if (result.bytes() > 8) {
  1335. DEBUG_MSG_P(PSTR("[IR] IN protocol %d state %s\n"),
  1336. static_cast<int>(result.type()), ir::state::value::encode(result.state()).c_str());
  1337. } else {
  1338. DEBUG_MSG_P(PSTR("[IR] IN protocol %d value %s bits %hu\n"),
  1339. static_cast<int>(result.type()), ir::simple::value::encode(result.value()).c_str(), result.bits());
  1340. }
  1341. }
  1342. } // namespace debug
  1343. #endif
  1344. namespace rx {
  1345. // TODO: rpnlib support like with rfbridge stringified callbacks?
  1346. void process(DecodeResult&& result) {
  1347. #if DEBUG_SUPPORT
  1348. ir::debug::log(result);
  1349. #endif
  1350. #if TERMINAL_SUPPORT
  1351. ir::terminal::process(result);
  1352. #endif
  1353. #if MQTT_SUPPORT
  1354. ir::mqtt::process(result);
  1355. #endif
  1356. }
  1357. // IRrecv uses os timers to schedule things, isr and the system task do the actual processing
  1358. // Unless `bufferSave()` is set to `true`, raw value buffers will be shared with the ISR task.
  1359. // After `decode()` call, `result` object does not store the actual data though, but references
  1360. // the specific buffer that was allocated by the `instance` constructor.
  1361. void loop() {
  1362. static ::decode_results result;
  1363. if (internal::instance->decode(&result)) {
  1364. if (result.overflow) {
  1365. return;
  1366. }
  1367. if ((result.decode_type == decode_type_t::UNKNOWN) && !internal::unknown) {
  1368. return;
  1369. }
  1370. static unsigned long last { millis() - internal::delay - 1ul };
  1371. unsigned long ts { millis() };
  1372. if (ts - last < internal::delay) {
  1373. return;
  1374. }
  1375. last = ts;
  1376. process(DecodeResult(result));
  1377. }
  1378. }
  1379. } // namespace rx
  1380. #if RELAY_SUPPORT
  1381. namespace relay {
  1382. namespace settings {
  1383. String relayOn(size_t id) {
  1384. return getSetting({"irRelayOn", id});
  1385. }
  1386. String relayOff(size_t id) {
  1387. return getSetting({"irRelayOff", id});
  1388. }
  1389. } // namespace settings
  1390. namespace internal {
  1391. void callback(size_t id, bool status) {
  1392. auto cmd = status
  1393. ? settings::relayOn(id)
  1394. : settings::relayOff(id);
  1395. if (!cmd.length()) {
  1396. return;
  1397. }
  1398. StringView view{cmd};
  1399. ir::tx::enqueue(ir::simple::parse(view));
  1400. }
  1401. } // namespace internal
  1402. void setup() {
  1403. ::relayOnStatusNotify(internal::callback);
  1404. ::relayOnStatusChange(internal::callback);
  1405. }
  1406. } // namespace relay
  1407. #endif
  1408. void configure() {
  1409. rx::configure();
  1410. tx::configure();
  1411. #if MQTT_SUPPORT
  1412. mqtt::configure();
  1413. #endif
  1414. }
  1415. void setup() {
  1416. auto rxPin = gpioRegister(rx::settings::pin());
  1417. if (rxPin) {
  1418. DEBUG_MSG_P(PSTR("[IR] Receiver on GPIO%hhu\n"), rxPin->pin());
  1419. } else {
  1420. DEBUG_MSG_P(PSTR("[IR] No receiver configured\n"));
  1421. }
  1422. auto txPin = gpioRegister(tx::settings::pin());
  1423. if (txPin) {
  1424. DEBUG_MSG_P(PSTR("[IR] Transmitter on GPIO%hhu\n"), txPin->pin());
  1425. } else {
  1426. DEBUG_MSG_P(PSTR("[IR] No transmitter configured\n"));
  1427. }
  1428. if (!rxPin && !txPin) {
  1429. return;
  1430. }
  1431. espurnaRegisterReload(configure);
  1432. configure();
  1433. if (rxPin && txPin) {
  1434. ::espurnaRegisterLoop([]() {
  1435. ir::rx::loop();
  1436. ir::tx::loop();
  1437. });
  1438. } else if (rxPin) {
  1439. ::espurnaRegisterLoop([]() {
  1440. ir::rx::loop();
  1441. });
  1442. } else if (txPin) {
  1443. ::espurnaRegisterLoop([]() {
  1444. ir::tx::loop();
  1445. });
  1446. }
  1447. if (txPin) {
  1448. #if MQTT_SUPPORT
  1449. ir::mqtt::setup();
  1450. #endif
  1451. #if RELAY_SUPPORT
  1452. ir::relay::setup();
  1453. #endif
  1454. #if TERMINAL_SUPPORT
  1455. ir::terminal::setup();
  1456. #endif
  1457. }
  1458. if (txPin) {
  1459. tx::setup(std::move(txPin));
  1460. }
  1461. if (rxPin) {
  1462. rx::setup(std::move(rxPin));
  1463. }
  1464. }
  1465. } // namespace
  1466. } // namespace ir
  1467. #if IR_TEST_SUPPORT
  1468. namespace ir {
  1469. namespace {
  1470. namespace test {
  1471. // TODO: may be useful if struct and values comparison error dump happens. but, not really nice looking for structs b/c of the length and no field highlight
  1472. #if 0
  1473. String serialize(const ::ir::simple::Payload& payload) {
  1474. String out;
  1475. out.reserve(128);
  1476. out += F("{ .type=decode_type_t::");
  1477. out += typeToString(payload.type);
  1478. out += F(", ");
  1479. out += F(".value=");
  1480. out += ::ir::simple::value::encode(payload.value);
  1481. out += F(", ");
  1482. out += F(".bits=");
  1483. out += String(payload.bits, 10);
  1484. out += F(", ");
  1485. out += F(".repeats=");
  1486. out += String(payload.repeats, 10);
  1487. out += F(", ");
  1488. out += F(".series=");
  1489. out += String(payload.series, 10);
  1490. out += F(", ");
  1491. out += F(".delay=");
  1492. out += String(payload.delay, 10);
  1493. out += F(" }");
  1494. return out;
  1495. }
  1496. String serialize(const ::ir::raw::Payload& payload) {
  1497. String out;
  1498. out.reserve(128);
  1499. out += F("{ .frequency=");
  1500. out += String(payload.frequency, 10);
  1501. out += F(", ");
  1502. out += F(".series=");
  1503. out += String(payload.series, 10);
  1504. out += F(", ");
  1505. out += F(".delay=");
  1506. out += String(payload.delay, 10);
  1507. out += F(", ");
  1508. out += F(".time[");
  1509. out += String(payload.time.size(), 10);
  1510. out += F("]={");
  1511. bool comma { false };
  1512. for (auto& value : payload.time) {
  1513. if (comma) {
  1514. out += F(", ");
  1515. }
  1516. out += String(value, 10);
  1517. comma = true;
  1518. }
  1519. out += F("} }");
  1520. return out;
  1521. }
  1522. #endif
  1523. struct Report {
  1524. Report(int line, String&& repr) :
  1525. _line(line),
  1526. _repr(std::move(repr))
  1527. {}
  1528. int line() const {
  1529. return _line;
  1530. }
  1531. const String& repr() const {
  1532. return _repr;
  1533. }
  1534. private:
  1535. int _line;
  1536. String _repr;
  1537. };
  1538. struct NoopPayloadSender : public ir::tx::ReschedulablePayload {
  1539. NoopPayloadSender(uint8_t series, unsigned long delay) :
  1540. ir::tx::ReschedulablePayload(series, delay)
  1541. {}
  1542. bool send(IRsend&) const override {
  1543. return series();
  1544. }
  1545. };
  1546. using Reports = std::vector<Report>;
  1547. struct Context {
  1548. struct View {
  1549. explicit View(Context& context) :
  1550. _context(context)
  1551. {}
  1552. template <typename... Args>
  1553. void report(Args&&... args) {
  1554. _context.report(std::forward<Args>(args)...);
  1555. }
  1556. private:
  1557. Context& _context;
  1558. };
  1559. using Runner = void(*)(View&);
  1560. using Runners = std::initializer_list<Runner>;
  1561. Context(Runners runners) :
  1562. _begin(std::begin(runners)),
  1563. _end(std::end(runners))
  1564. {
  1565. run();
  1566. }
  1567. #if DEBUG_SUPPORT
  1568. ~Context() {
  1569. DEBUG_MSG_P(PSTR("[IR TEST] %s\n"),
  1570. _reports.size() ? "FAILED" : "SUCCESS");
  1571. for (auto& report : _reports) {
  1572. DEBUG_MSG_P(PSTR("[IR TEST] " __FILE__ ":%d '%.*s'\n"),
  1573. report.line(), report.repr().length(), report.repr().c_str());
  1574. }
  1575. }
  1576. #endif
  1577. template <typename... Args>
  1578. void report(Args&&... args) {
  1579. _reports.emplace_back(std::forward<Args>(args)...);
  1580. }
  1581. private:
  1582. void run() {
  1583. View view(*this);
  1584. for (auto* it = _begin; it != _end; ++it) {
  1585. (*it)(view);
  1586. }
  1587. }
  1588. const Runner* _begin;
  1589. const Runner* _end;
  1590. Reports _reports;
  1591. };
  1592. // TODO: unity and pio-test? would need to:
  1593. // - use `test_build_project_src = yes` in the .ini
  1594. // - disable `DEBUG_SERIAL_SUPPORT` in case it's on `Serial` or anything else allowing output to the `Serial`
  1595. // (some code gets automatically generated when `pio test` is called that contains setUp(), tearDown(), etc.)
  1596. // - have more preprocessor-wrapped chunks
  1597. // - not depend on destructors, since unity uses setjmp and longjmp
  1598. // (or use `-DUNITY_EXCLUDE_SETJMP_H`)
  1599. // TODO: anything else header-only? may be a problem though with c++ approach, as most popular frameworks depend on std::ostream
  1600. // TODO: for parsers specifically, some fuzzing to randomize inputs and test order could be useful
  1601. // (also, extending the current set of tests and / or having some helper macro that can fill the boilerplate)
  1602. // As a (temporary?) solution for right now, have these 4 macros that setup a Context object and a list of test runners.
  1603. // Each runner may call `IR_TEST(<something resolving to bool>)` to immediatly exit current block on failure and save report to the Context object.
  1604. // On destruction of the Context object, every report is printed to the debug output.
  1605. #define IR_TEST_SETUP_BEGIN() Context runner ## __FILE__ ## __LINE__ {
  1606. #define IR_TEST_SETUP_END() }
  1607. #define IR_TEST_RUNNER() [](Context::View& __context_view)
  1608. #define IR_TEST(EXPRESSION) {\
  1609. if (!(EXPRESSION)) {\
  1610. __context_view.report(__LINE__, F(#EXPRESSION));\
  1611. return;\
  1612. }\
  1613. }
  1614. void setup() {
  1615. IR_TEST_SETUP_BEGIN() {
  1616. IR_TEST_RUNNER() {
  1617. IR_TEST(!ir::simple::parse(""));
  1618. },
  1619. IR_TEST_RUNNER() {
  1620. IR_TEST(!ir::simple::parse(","));
  1621. },
  1622. IR_TEST_RUNNER() {
  1623. IR_TEST(!ir::simple::parse("999::"));
  1624. },
  1625. IR_TEST_RUNNER() {
  1626. IR_TEST(!ir::simple::parse("-5:doesntmatter"));
  1627. },
  1628. IR_TEST_RUNNER() {
  1629. IR_TEST(!ir::simple::parse("2:0:31"));
  1630. },
  1631. IR_TEST_RUNNER() {
  1632. IR_TEST(!ir::simple::parse("2:012:31"));
  1633. },
  1634. IR_TEST_RUNNER() {
  1635. IR_TEST(!ir::simple::parse("2:112233445566778899AA:31"));
  1636. },
  1637. IR_TEST_RUNNER() {
  1638. IR_TEST(::ir::simple::value::encode(0xffaabbccddee) == F("FFAABBCCDDEE"));
  1639. },
  1640. IR_TEST_RUNNER() {
  1641. IR_TEST(::ir::simple::value::encode(0xfaabbccddee) == F("0FAABBCCDDEE"));
  1642. },
  1643. IR_TEST_RUNNER() {
  1644. IR_TEST(::ir::simple::value::encode(0xee) == F("EE"));
  1645. },
  1646. IR_TEST_RUNNER() {
  1647. IR_TEST(::ir::simple::value::encode(0) == F("00"));
  1648. },
  1649. IR_TEST_RUNNER() {
  1650. auto result = ir::simple::parse("2:7FAABBCC:31");
  1651. IR_TEST(result.has_value());
  1652. auto& payload = result.value();
  1653. IR_TEST(payload.type == decode_type_t::RC6);
  1654. IR_TEST(payload.value == static_cast<uint64_t>(0x7faabbcc));
  1655. IR_TEST(payload.bits == 31);
  1656. },
  1657. IR_TEST_RUNNER() {
  1658. auto result = ir::simple::parse("15:AABBCCDD:25:3");
  1659. IR_TEST(result.has_value());
  1660. auto& payload = result.value();
  1661. IR_TEST(payload.type == decode_type_t::COOLIX);
  1662. IR_TEST(payload.value == static_cast<uint64_t>(0xaabbccdd));
  1663. IR_TEST(payload.bits == 25);
  1664. IR_TEST(payload.repeats == 3);
  1665. },
  1666. IR_TEST_RUNNER() {
  1667. auto result = ir::simple::parse("10:0FEFEFEF:21:2:5:500");
  1668. IR_TEST(result.has_value());
  1669. auto& payload = result.value();
  1670. IR_TEST(payload.type == decode_type_t::LG);
  1671. IR_TEST(payload.value == static_cast<uint64_t>(0x0fefefef));
  1672. IR_TEST(payload.bits == 21);
  1673. IR_TEST(payload.repeats == 2);
  1674. IR_TEST(payload.series == 5);
  1675. IR_TEST(payload.delay == 500);
  1676. },
  1677. IR_TEST_RUNNER() {
  1678. auto result = ir::simple::parse("20:1122AABBCCDDEEFF:64:2:3:1000");
  1679. IR_TEST(result.has_value());
  1680. auto ptr = std::make_unique<NoopPayloadSender>(
  1681. result->series, result->delay);
  1682. IR_TEST(ptr->delay() == 1000);
  1683. IRsend sender(GPIO_NONE);
  1684. IR_TEST(ptr->send(sender));
  1685. IR_TEST(ptr->reschedule());
  1686. IR_TEST(ptr->send(sender));
  1687. IR_TEST(ptr->reschedule());
  1688. IR_TEST(ptr->send(sender));
  1689. IR_TEST(!ptr->reschedule());
  1690. },
  1691. IR_TEST_RUNNER() {
  1692. IR_TEST(!ir::state::parse(""));
  1693. },
  1694. IR_TEST_RUNNER() {
  1695. IR_TEST(!ir::state::parse(":"));
  1696. },
  1697. IR_TEST_RUNNER() {
  1698. IR_TEST(!ir::state::parse("-1100,100,150"));
  1699. },
  1700. IR_TEST_RUNNER() {
  1701. IR_TEST(!ir::state::parse("25:"));
  1702. },
  1703. IR_TEST_RUNNER() {
  1704. IR_TEST(!ir::state::parse("30:C"));
  1705. },
  1706. IR_TEST_RUNNER() {
  1707. IR_TEST(ir::state::parse("45:CD"));
  1708. },
  1709. IR_TEST_RUNNER() {
  1710. auto result = ir::state::parse("20:C7B7966A9B29CD3C5F2AC03B91B0B221");
  1711. IR_TEST(result.has_value());
  1712. auto& payload = result.value();
  1713. IR_TEST(payload.type == decode_type_t::MITSUBISHI_AC);
  1714. const uint8_t raw[] {
  1715. 0xc7, 0xb7, 0x96, 0x6a,
  1716. 0x9b, 0x29, 0xcd, 0x3c,
  1717. 0x5f, 0x2a, 0xc0, 0x3b,
  1718. 0x91, 0xb0, 0xb2, 0x21};
  1719. IR_TEST(payload.value.size() == sizeof(raw));
  1720. IR_TEST(std::equal(std::begin(payload.value), std::end(payload.value),
  1721. std::begin(raw)));
  1722. },
  1723. IR_TEST_RUNNER() {
  1724. IR_TEST(!ir::raw::parse("-1:1:500:,200,150,250,50,100,100,150"));
  1725. },
  1726. IR_TEST_RUNNER() {
  1727. auto result = ir::raw::parse("38:1:500:100,200,150,250,50,100,100,150");
  1728. IR_TEST(result.has_value());
  1729. auto& payload = result.value();
  1730. IR_TEST(payload.frequency == 38);
  1731. IR_TEST(payload.series == 1);
  1732. IR_TEST(payload.delay == 500);
  1733. decltype(ir::raw::Payload::time) expected_time {
  1734. 100, 200, 150, 250, 50, 100, 100, 150};
  1735. IR_TEST(expected_time == payload.time);
  1736. },
  1737. IR_TEST_RUNNER() {
  1738. const uint16_t raw[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
  1739. IR_TEST(::ir::raw::time::encode(std::begin(raw), std::end(raw)) == F("2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32"));
  1740. }
  1741. }
  1742. IR_TEST_SETUP_END();
  1743. }
  1744. } // namespace test
  1745. } // namespace
  1746. } // namespace ir
  1747. #endif
  1748. void irSetup() {
  1749. #if IR_TEST_SUPPORT
  1750. ir::test::setup();
  1751. #endif
  1752. ir::setup();
  1753. }
  1754. #endif