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.

202 lines
6.1 KiB

  1. #pragma once
  2. #include <array>
  3. #include "common.h"
  4. #include "esphome/core/component.h"
  5. #include "esphome/core/esphal.h"
  6. #include "esphome/components/i2c/i2c.h"
  7. namespace esphome {
  8. namespace yeelight {
  9. namespace bs2 {
  10. static const uint8_t MSG_LEN = 7;
  11. using MSG = uint8_t[7];
  12. static const MSG READY_FOR_EV = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
  13. static const MSG TURN_ON = { 0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00 };
  14. static const MSG TURN_OFF = { 0x02, 0x03, 0x0C, 0x00, 0x64, 0x00, 0x00 };
  15. static const MSG SET_LEVEL_1 = { 0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00 };
  16. static const MSG SET_LEVEL_2 = { 0x02, 0x03, 0x5F, 0x00, 0x64, 0x00, 0x00 };
  17. static const MSG SET_LEVEL_3 = { 0x02, 0x03, 0x5F, 0x80, 0x64, 0x00, 0x00 };
  18. static const MSG SET_LEVEL_4 = { 0x02, 0x03, 0x5F, 0xC0, 0x64, 0x00, 0x00 };
  19. static const MSG SET_LEVEL_5 = { 0x02, 0x03, 0x5F, 0xE0, 0x64, 0x00, 0x00 };
  20. static const MSG SET_LEVEL_6 = { 0x02, 0x03, 0x5F, 0xF0, 0x64, 0x00, 0x00 };
  21. static const MSG SET_LEVEL_7 = { 0x02, 0x03, 0x5F, 0xF8, 0x64, 0x00, 0x00 };
  22. static const MSG SET_LEVEL_8 = { 0x02, 0x03, 0x5F, 0xFC, 0x64, 0x00, 0x00 };
  23. static const MSG SET_LEVEL_9 = { 0x02, 0x03, 0x5F, 0xFE, 0x64, 0x00, 0x00 };
  24. static const MSG SET_LEVEL_10 = { 0x02, 0x03, 0x5F, 0xFF, 0x64, 0x00, 0x00 };
  25. enum FrontPanelButton {
  26. ButtonUnknown,
  27. ButtonPower,
  28. ButtonColor,
  29. ButtonSlider
  30. };
  31. enum FrontPanelEventType {
  32. TypeUnknown,
  33. TypeTouch,
  34. TypeRelease
  35. };
  36. class FrontPanelEvent {
  37. public:
  38. bool valid;
  39. FrontPanelEventType type;
  40. FrontPanelButton button;
  41. uint8_t level;
  42. MSG message;
  43. void parse(uint8_t *m) {
  44. memcpy(message, m, MSG_LEN);
  45. type = TypeUnknown;
  46. button = ButtonUnknown;
  47. valid = true;
  48. level = 0;
  49. // All events start with 04:04:01:00.
  50. if (m[0] != 0x04 || m[1] != 0x04 || m[2] != 0x01 || m[3] != 0x00) {
  51. valid = false;
  52. return;
  53. }
  54. // Next byte determines the button that is touched.
  55. // All remaining bytes determine the event for that button.
  56. switch (m[4]) {
  57. case 0x01:
  58. button = ButtonPower;
  59. if (m[5] == 0x01 && m[6] == 0x03) {
  60. type = TypeTouch;
  61. } else if (m[5] == 0x02 && m[6] == 0x04) {
  62. type = TypeRelease;
  63. } else {
  64. valid = false;
  65. }
  66. break;
  67. case 0x02:
  68. button = ButtonColor;
  69. if (m[5] == 0x01 && m[6] == 0x04) {
  70. type = TypeTouch;
  71. } else if (m[5] == 0x02 && m[6] == 0x05) {
  72. type = TypeRelease;
  73. } else {
  74. valid = false;
  75. }
  76. break;
  77. case 0x03:
  78. case 0x04:
  79. button = ButtonSlider;
  80. type = m[4] == 0x03 ? TypeTouch : TypeRelease;
  81. if ((m[6] - m[5] - m[4] - 0x01) != 0) {
  82. valid = false;
  83. } else if (m[5] > 0x16 || m[5] < 0x01) {
  84. valid = false;
  85. } else {
  86. level = 0x17 - m[5];
  87. }
  88. break;
  89. default:
  90. valid = false;
  91. return;
  92. }
  93. }
  94. void log() {
  95. if (button == ButtonSlider) {
  96. ESP_LOGI(TAG, "Event %0x:%0x:%0x:%0x:%0x:%0x:%0x => ok=%s, button=%s, type=%s, level=%d",
  97. message[0], message[1], message[2], message[3], message[4],
  98. message[5], message[6],
  99. (valid ? "Y" : "N"), button_str_(), type_str_(), level);
  100. } else {
  101. ESP_LOGI(TAG, "Event %0x:%0x:%0x:%0x:%0x:%0x:%0x => ok=%s, button=%s, type=%s",
  102. message[0], message[1], message[2], message[3], message[4],
  103. message[5], message[6],
  104. (valid ? "Y" : "N"), button_str_(), type_str_());
  105. }
  106. }
  107. protected:
  108. const char *button_str_() {
  109. switch (button) {
  110. case ButtonPower: return "POWER"; break;
  111. case ButtonColor: return "COLOR"; break;
  112. case ButtonSlider: return "SLIDER"; break;
  113. default: return "ERROR"; break;
  114. }
  115. }
  116. const char *type_str_() {
  117. switch (type) {
  118. case TypeTouch: return "TOUCH"; break;
  119. case TypeRelease: return "RELEASE"; break;
  120. default: return "ERROR";
  121. }
  122. }
  123. };
  124. class FrontPanelHAL : public Component, public i2c::I2CDevice {
  125. public:
  126. FrontPanelEvent ev;
  127. void set_trigger_pin(GPIOPin *pin) { trigger_pin_ = pin; }
  128. void setup() {
  129. ESP_LOGCONFIG(TAG, "Setting up I2C trigger pin interrupt...");
  130. trigger_pin_->setup();
  131. trigger_pin_->attach_interrupt(
  132. FrontPanelHAL::isr, this, FALLING);
  133. }
  134. void dump_config() {
  135. ESP_LOGCONFIG(TAG, "I2C");
  136. LOG_PIN(" Interrupt pin: ", trigger_pin_);
  137. }
  138. void loop() {
  139. if (queue_length_ > 0) {
  140. queue_length_ = 0;
  141. read_event_();
  142. }
  143. }
  144. protected:
  145. // The GPIO pin that is used by the front panel to notify the ESP that
  146. // a touch/release event can be read using I2C.
  147. GPIOPin *trigger_pin_;
  148. // The ISR that is used for handling event interrupts.
  149. static void isr(FrontPanelHAL *store);
  150. // The number of unhandled event interrupts.
  151. volatile int queue_length_ = 0;
  152. uint8_t message[MSG_LEN];
  153. void read_event_() {
  154. if (!write_bytes_raw(READY_FOR_EV, MSG_LEN)) {
  155. return;
  156. }
  157. if (!read_bytes_raw(message, MSG_LEN)) {
  158. return;
  159. }
  160. ev.parse(message);
  161. ev.log();
  162. }
  163. };
  164. /**
  165. * This ISR is used to handle IRQ triggers from the front panel.
  166. *
  167. * The front panel pulls the trigger pin low when a new event
  168. * is available. All we do here to handle the interrupt, is
  169. * increment a simple queue length counter. Reading the event
  170. * from the I2C bus will be handled in the main loop, based
  171. * on this counter.
  172. */
  173. void ICACHE_RAM_ATTR HOT FrontPanelHAL::isr(FrontPanelHAL *store) {
  174. store->queue_length_++;
  175. }
  176. } // namespace bs2
  177. } // namespace yeelight
  178. } // namespace esphome