Fork of the espurna firmware for `mhsw` switches
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.

198 lines
6.7 KiB

  1. /*
  2. KingArt Cover/Shutter/Blind/Curtain support for ESPURNA
  3. Based on xdrv_19_ps16dz.dimmer.ino, PS_16_DZ dimmer support for Tasmota
  4. Copyright (C) 2019 by Albert Weterings
  5. */
  6. #include "curtain_kingart.h"
  7. #if KINGART_CURTAIN_SUPPORT
  8. #include "ntp.h"
  9. #include "mqtt.h"
  10. #ifndef KINGART_CURTAIN_PORT
  11. #define KINGART_CURTAIN_PORT Serial // Hardware serial port by default
  12. #endif
  13. #ifndef KINGART_CURTAIN_BUFFER_SIZE
  14. #define KINGART_CURTAIN_BUFFER_SIZE 100 // Local UART buffer size
  15. #endif
  16. #define KINGART_CURTAIN_TERMINATION '\e' // Termination character after each message
  17. #define KINGART_CURTAIN_BAUDRATE 19200 // Serial speed is fixed for the device
  18. char _KACurtainBuffer[KINGART_CURTAIN_BUFFER_SIZE];
  19. bool _KACurtainNewData = false;
  20. // -----------------------------------------------------------------------------
  21. // Private
  22. // -----------------------------------------------------------------------------
  23. void _KACurtainSend(const char * tx_buffer) {
  24. KINGART_CURTAIN_PORT.print(tx_buffer);
  25. KINGART_CURTAIN_PORT.print(KINGART_CURTAIN_TERMINATION);
  26. KINGART_CURTAIN_PORT.flush();
  27. }
  28. void _KACurtainReceiveUART() {
  29. static unsigned char ndx = 0;
  30. while (KINGART_CURTAIN_PORT.available() > 0 && !_KACurtainNewData) {
  31. char rc = KINGART_CURTAIN_PORT.read();
  32. if (rc != KINGART_CURTAIN_TERMINATION) {
  33. _KACurtainBuffer[ndx] = rc;
  34. if (ndx < KINGART_CURTAIN_BUFFER_SIZE - 1) ndx++;
  35. } else {
  36. _KACurtainBuffer[ndx] = '\0';
  37. _KACurtainNewData = true;
  38. ndx = 0;
  39. }
  40. }
  41. }
  42. /*
  43. Buttons on the device will move Cover/Shutter/Blind/Curtain up/open or down/close On the end of
  44. every movement the unit reports the last action and posiston over MQTT topic {hostname}/curtain
  45. RAW paylod format looks like:
  46. AT+UPDATE="switch":"on","setclose":13
  47. AT+UPDATE="switch":"off","setclose":38
  48. AT+UPDATE="switch":"pause","setclose":75
  49. The device is listening to MQTT topic {hostname}/curtain/set, to which you can send:
  50. - position value, in range from 0 to 100
  51. - "pause", to stop the movement.
  52. The device will determine the direction all by itself.
  53. # Set the Cover / Shutter / Blind / Curtain run time
  54. The factory default Open and Close run time for the switch is 50 seconds, and it must be set to
  55. an accurate run time for smooth working. Some motors do not have the resistance stop function,
  56. so when the Cover/Shutter/Blind/Curtain track open or close to the maximum length, but the motor keeps on running.
  57. This might cause damage on the motor and the switch, it also wastes a lot of energy. In order
  58. to protect the motor, this switch designed with a time setting function. After setting up the run time,
  59. the switch will automaticly stop when the track reaches its limits. The run time setting is also helpful
  60. for the accurate control when manually controlling the device via the touch panel.
  61. After installing the switch and connecting the switch for the very first time:
  62. - First, it will automatically close the Cover/Shutter/Blind/Curtain to the maximum.
  63. - Press and hold the touch interface pause button for around 4 seconds until the red background
  64. led lights up and starts blinking. Then, press the open touch button so start the opening process.
  65. - When cover is fully open, press the Open or Close button to stop the timer and save the calculated run time.
  66. To configure the device:
  67. - Press up/down for 5 seconds to bring device into AP mode. After pressing up/down again, device will restart in normal mode.
  68. */
  69. void _KACurtainResult() {
  70. if (_KACurtainNewData) {
  71. // Need to send confiramtion to the N76E003AT20 that message is received
  72. _KACurtainSend("AT+SEND=ok");
  73. // We don't handle "setclose" any other way, simply redirect payload value
  74. const String buffer(_KACurtainBuffer);
  75. #if MQTT_SUPPORT
  76. int setclose_idx = buffer.indexOf("setclose");
  77. if (setclose_idx > 0) {
  78. auto position = buffer.substring(setclose_idx + strlen("setclose") + 2, buffer.length());
  79. int leftovers = position.indexOf(',');
  80. if (leftovers > 0) {
  81. position = position.substring(0, leftovers);
  82. }
  83. mqttSend(MQTT_TOPIC_CURTAIN, position.c_str());
  84. }
  85. #endif // MQTT_SUPPORT
  86. // Handle configuration button presses
  87. if (buffer.indexOf("enterESPTOUCH") > 0) {
  88. wifiStartAP();
  89. } else if (buffer.indexOf("exitESPTOUCH") > 0) {
  90. deferredReset(100, CUSTOM_RESET_HARDWARE);
  91. }
  92. _KACurtainNewData = false;
  93. }
  94. }
  95. // %d = now() / time_t / NTP timestamp in seconds
  96. // %03u = millis() / uint32_t / we need last 3 digits
  97. // %s = char strings for various actions
  98. // Tell N76E003AT20 to stop moving and report current position
  99. void _KACurtainPause(const char * message) {
  100. char tx_buffer[80] = {0};
  101. snprintf_P(
  102. tx_buffer, sizeof(tx_buffer),
  103. PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\""),
  104. now(), millis() % 1000,
  105. message
  106. );
  107. _KACurtainSend(tx_buffer);
  108. }
  109. // Tell N76E003AT20 to go to position X (based on X N76E003AT20 decides to go up or down)
  110. void _KACurtainSetclose(const char * message) {
  111. char tx_buffer[80] = {0};
  112. snprintf_P(
  113. tx_buffer, sizeof(tx_buffer),
  114. PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\",\"setclose\":%s"),
  115. now(), millis() % 1000,
  116. "off", message
  117. );
  118. _KACurtainSend(tx_buffer);
  119. }
  120. #if MQTT_SUPPORT
  121. void _KACurtainActionSelect(const char * message) {
  122. if (strcmp(message, "pause") == 0) {
  123. _KACurtainPause(message);
  124. } else {
  125. _KACurtainSetclose(message);
  126. }
  127. }
  128. void _KACurtainCallback(unsigned int type, const char * topic, char * payload) {
  129. if (type == MQTT_CONNECT_EVENT) {
  130. mqttSubscribe(MQTT_TOPIC_CURTAIN);
  131. }
  132. if (type == MQTT_MESSAGE_EVENT) {
  133. // Match topic
  134. const String t = mqttMagnitude(const_cast<char*>(topic));
  135. if (t.equals(MQTT_TOPIC_CURTAIN)) {
  136. _KACurtainActionSelect(payload);
  137. }
  138. }
  139. }
  140. #endif // MQTT_SUPPORT
  141. // -----------------------------------------------------------------------------
  142. // SETUP & LOOP
  143. // -----------------------------------------------------------------------------
  144. void _KACurtainLoop() {
  145. _KACurtainReceiveUART();
  146. _KACurtainResult();
  147. }
  148. void kingartCurtainSetup() {
  149. // Init port to receive and send messages
  150. KINGART_CURTAIN_PORT.begin(KINGART_CURTAIN_BAUDRATE);
  151. // Register MQTT callback only when supported
  152. #if MQTT_SUPPORT
  153. mqttRegister(_KACurtainCallback);
  154. #endif // MQTT_SUPPORT
  155. // Register loop to poll the UART for new messages
  156. espurnaRegisterLoop(_KACurtainLoop);
  157. }
  158. #endif // KINGART_CURTAIN_SUPPORT