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.

280 lines
7.7 KiB

  1. /*
  2. LIGHT MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  6. #include <Ticker.h>
  7. Ticker colorTicker;
  8. bool _lightState = false;
  9. unsigned int _lightColor[3] = {0};
  10. #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
  11. #include <my9291.h>
  12. my9291 * _my9291;
  13. #endif
  14. #ifndef LIGHT_PWM_FREQUENCY
  15. #define LIGHT_PWM_FREQUENCY (1000)
  16. #endif
  17. #ifndef LIGHT_PWM_RANGE
  18. #define LIGHT_PWM_RANGE (255)
  19. #endif
  20. // -----------------------------------------------------------------------------
  21. // UTILS
  22. // -----------------------------------------------------------------------------
  23. void color_string2array(const char * rgb, unsigned int * array) {
  24. char * p = (char *) rgb;
  25. if (strlen(p) == 0) return;
  26. // if color begins with a # then assume HEX RGB
  27. if (p[0] == '#') {
  28. ++p;
  29. unsigned long value = strtol(p, NULL, 16);
  30. array[0] = (value >> 16) & 0xFF;
  31. array[1] = (value >> 8) & 0xFF;
  32. array[2] = (value) & 0xFF;
  33. // it's a temperature
  34. } else if (p[strlen(p)-1] == 'K') {
  35. p[strlen(p)-1] = 0;
  36. unsigned int temperature = atoi(p);
  37. color_temperature2array(temperature, array);
  38. // otherwise assume decimal values separated by commas
  39. } else {
  40. char * tok;
  41. tok = strtok(p, ",");
  42. array[0] = atoi(tok);
  43. tok = strtok(NULL, ",");
  44. // if there are more than one value assume R,G,B
  45. if (tok != NULL) {
  46. array[1] = atoi(tok);
  47. tok = strtok(NULL, ",");
  48. if (tok != NULL) {
  49. array[2] = atoi(tok);
  50. } else {
  51. array[2] = 0;
  52. }
  53. // only one value set red, green and blue to the same value
  54. } else {
  55. array[2] = array[1] = array[0];
  56. }
  57. }
  58. }
  59. void color_array2rgb(unsigned int * array, char * rgb) {
  60. unsigned long value = array[0];
  61. value = (value << 8) + array[1];
  62. value = (value << 8) + array[2];
  63. sprintf(rgb, "#%06X", value);
  64. }
  65. // Thanks to Sacha Telgenhof for sharing this code in his AiLight library
  66. // https://github.com/stelgenhof/AiLight
  67. void color_temperature2array(unsigned int temperature, unsigned int * array) {
  68. // Force boundaries and conversion
  69. temperature = constrain(temperature, 1000, 40000) / 100;
  70. // Calculate colors
  71. unsigned int red = (temperature <= 66)
  72. ? LIGHT_MAX_VALUE
  73. : 329.698727446 * pow((temperature - 60), -0.1332047592);
  74. unsigned int green = (temperature <= 66)
  75. ? 99.4708025861 * log(temperature) - 161.1195681661
  76. : 288.1221695283 * pow(temperature, -0.0755148492);
  77. unsigned int blue = (temperature >= 66)
  78. ? LIGHT_MAX_VALUE
  79. : ((temperature <= 19)
  80. ? 0
  81. : 138.5177312231 * log(temperature - 10) - 305.0447927307);
  82. // Save values
  83. array[0] = constrain(red, 0, LIGHT_MAX_VALUE);
  84. array[1] = constrain(green, 0, LIGHT_MAX_VALUE);
  85. array[2] = constrain(blue, 0, LIGHT_MAX_VALUE);
  86. }
  87. // Converts a color intensity value (0..255) to a pwm value
  88. // This takes care of positive or negative logic
  89. unsigned int intensity2pwm(unsigned int intensity)
  90. {
  91. unsigned int pwm;
  92. // Support integer multiples of 256 (-1) for the LIGHT_PWM_RANGE
  93. // The divide should happen at compile time
  94. pwm = intensity * ((LIGHT_PWM_RANGE+1) / (LIGHT_MAX_VALUE+1));
  95. #if RGBW_INVERSE_LOGIC != 1
  96. pwm = LIGHT_PWM_RANGE - pwm;
  97. #endif
  98. return pwm;
  99. }
  100. // -----------------------------------------------------------------------------
  101. // PROVIDER
  102. // -----------------------------------------------------------------------------
  103. void _lightProviderSet(bool state, unsigned int red, unsigned int green, unsigned int blue) {
  104. unsigned int white = 0;
  105. #if (LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
  106. // If all set to the same value use white instead
  107. if ((red == green) && (green == blue)) {
  108. white = red;
  109. red = green = blue = 0;
  110. }
  111. #endif
  112. #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
  113. _my9291->setState(state);
  114. _my9291->setColor((my9291_color_t) { red, green, blue, white });
  115. #endif
  116. #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
  117. // Check state
  118. if (!state) red = green = blue = white = 0;
  119. analogWrite(RGBW_RED_PIN, intensity2pwm(red));
  120. analogWrite(RGBW_GREEN_PIN, intensity2pwm(green));
  121. analogWrite(RGBW_BLUE_PIN, intensity2pwm(blue));
  122. #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
  123. analogWrite(RGBW_WHITE_PIN, intensity2pwm(white));
  124. #endif
  125. #endif
  126. }
  127. // -----------------------------------------------------------------------------
  128. // LIGHT MANAGEMENT
  129. // -----------------------------------------------------------------------------
  130. void lightState(bool state) {
  131. _lightState = state;
  132. _lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
  133. }
  134. bool lightState() {
  135. return _lightState;
  136. }
  137. void lightColor(const char * color, bool save, bool forward) {
  138. color_string2array(color, _lightColor);
  139. _lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
  140. char rgb[8];
  141. color_array2rgb(_lightColor, rgb);
  142. // Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
  143. if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave);
  144. // Report color to MQTT broker
  145. if (forward) mqttSend(MQTT_TOPIC_COLOR, rgb);
  146. // Report color to WS clients
  147. char message[20];
  148. sprintf(message, "{\"color\": \"%s\"}", rgb);
  149. wsSend(message);
  150. }
  151. String lightColor() {
  152. char rgb[8];
  153. color_array2rgb(_lightColor, rgb);
  154. return String(rgb);
  155. }
  156. // -----------------------------------------------------------------------------
  157. // PERSISTANCE
  158. // -----------------------------------------------------------------------------
  159. void _lightColorSave() {
  160. setSetting("color", lightColor());
  161. saveSettings();
  162. }
  163. void _lightColorRestore() {
  164. String color = getSetting("color", LIGHT_DEFAULT_COLOR);
  165. color_string2array(color.c_str(), _lightColor);
  166. }
  167. // -----------------------------------------------------------------------------
  168. // MQTT
  169. // -----------------------------------------------------------------------------
  170. void lightMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  171. if (type == MQTT_CONNECT_EVENT) {
  172. mqttSubscribe(MQTT_TOPIC_COLOR);
  173. }
  174. if (type == MQTT_MESSAGE_EVENT) {
  175. // Match topic
  176. String t = mqttSubtopic((char *) topic);
  177. if (!t.equals(MQTT_TOPIC_COLOR)) return;
  178. lightColor(payload, true, mqttForward());
  179. }
  180. }
  181. // -----------------------------------------------------------------------------
  182. // SETUP
  183. // -----------------------------------------------------------------------------
  184. void lightSetup() {
  185. #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
  186. _my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND);
  187. #endif
  188. #if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
  189. analogWriteRange(LIGHT_PWM_RANGE);
  190. analogWriteFreq(LIGHT_PWM_FREQUENCY);
  191. pinMode(RGBW_RED_PIN, OUTPUT);
  192. pinMode(RGBW_GREEN_PIN, OUTPUT);
  193. pinMode(RGBW_BLUE_PIN, OUTPUT);
  194. #if LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW
  195. pinMode(RGBW_WHITE_PIN, OUTPUT);
  196. #endif
  197. #endif
  198. _lightColorRestore();
  199. // API entry points (protected with apikey)
  200. apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR,
  201. [](char * buffer, size_t len) {
  202. snprintf(buffer, len, "%s", lightColor().c_str());
  203. },
  204. [](const char * payload) {
  205. lightColor(payload, true, mqttForward());
  206. }
  207. );
  208. mqttRegister(lightMQTTCallback);
  209. }
  210. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE