xdrv_04_light.ino 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
  1. /*
  2. xdrv_04_light.ino - PWM, WS2812 and sonoff led support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. /*********************************************************************************************\
  16. * PWM, WS2812, Sonoff B1, AiLight, Sonoff Led and BN-SZ01, H801, MagicHome and Arilux
  17. *
  18. * light_type Module Color ColorTemp Modules
  19. * ---------- --------- ----- --------- ----------------------------
  20. * 1 PWM1 W no (Sonoff BN-SZ)
  21. * 2 PWM2 CW yes (Sonoff Led)
  22. * 3 PWM3 RGB no (H801, MagicHome and Arilux LC01)
  23. * 4 PWM4 RGBW no (H801, MagicHome and Arilux)
  24. * 5 PWM5 RGBCW yes (H801, Arilux LC11)
  25. * 9 reserved no
  26. * 10 reserved yes
  27. * 11 +WS2812 RGB(W) no (One WS2812 RGB or RGBW ledstrip)
  28. * 12 AiLight RGBW no
  29. * 13 Sonoff B1 RGBCW yes
  30. *
  31. * light_scheme WS2812 3+ Colors 1+2 Colors Effect
  32. * ------------ ------ --------- ---------- -----------------
  33. * 0 yes yes yes Color On/Off
  34. * 1 yes yes yes Wakeup light
  35. * 2 yes yes no Color cycle RGB
  36. * 3 yes yes no Color cycle RBG
  37. * 4 yes yes no Random RGB colors
  38. * 5 yes no no Clock
  39. * 6 yes no no Incandescent
  40. * 7 yes no no RGB
  41. * 8 yes no no Christmas
  42. * 9 yes no no Hanukkah
  43. * 10 yes no no Kwanzaa
  44. * 11 yes no no Rainbow
  45. * 12 yes no no Fire
  46. *
  47. \*********************************************************************************************/
  48. #define XDRV_04 4
  49. #define WS2812_SCHEMES 7 // Number of additional WS2812 schemes supported by xdrv_ws2812.ino
  50. enum LightCommands {
  51. CMND_COLOR, CMND_COLORTEMPERATURE, CMND_DIMMER, CMND_LED, CMND_LEDTABLE, CMND_FADE,
  52. CMND_PIXELS, CMND_RGBWWTABLE, CMND_ROTATION, CMND_SCHEME, CMND_SPEED, CMND_WAKEUP, CMND_WAKEUPDURATION,
  53. CMND_WHITE, CMND_WIDTH, CMND_CHANNEL, CMND_HSBCOLOR, CMND_UNDOCA };
  54. const char kLightCommands[] PROGMEM =
  55. D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_LED "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
  56. D_CMND_PIXELS "|" D_CMND_RGBWWTABLE "|" D_CMND_ROTATION "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
  57. D_CMND_WHITE "|" D_CMND_WIDTH "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR "|UNDOCA" ;
  58. struct LRgbColor {
  59. uint8_t R, G, B;
  60. };
  61. #define MAX_FIXED_COLOR 12
  62. const LRgbColor kFixedColor[MAX_FIXED_COLOR] PROGMEM =
  63. { 255,0,0, 0,255,0, 0,0,255, 228,32,0, 0,228,32, 0,32,228, 188,64,0, 0,160,96, 160,32,240, 255,255,0, 255,0,170, 255,255,255 };
  64. struct LWColor {
  65. uint8_t W;
  66. };
  67. #define MAX_FIXED_WHITE 4
  68. const LWColor kFixedWhite[MAX_FIXED_WHITE] PROGMEM = { 0, 255, 128, 32 };
  69. struct LCwColor {
  70. uint8_t C, W;
  71. };
  72. #define MAX_FIXED_COLD_WARM 4
  73. const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
  74. uint8_t ledTable[] = {
  75. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  76. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  77. 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4,
  78. 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
  79. 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14,
  80. 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22,
  81. 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32,
  82. 33, 33, 34, 35, 36, 36, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45,
  83. 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
  84. 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78,
  85. 80, 81, 82, 83, 85, 86, 87, 89, 90, 91, 93, 94, 95, 97, 98, 99,
  86. 101,102,104,105,107,108,110,111,113,114,116,117,119,121,122,124,
  87. 125,127,129,130,132,134,135,137,139,141,142,144,146,148,150,151,
  88. 153,155,157,159,161,163,165,166,168,170,172,174,176,178,180,182,
  89. 184,186,189,191,193,195,197,199,201,204,206,208,210,212,215,217,
  90. 219,221,224,226,228,231,233,235,238,240,243,245,248,250,253,255 };
  91. uint8_t light_entry_color[5];
  92. uint8_t light_current_color[5];
  93. uint8_t light_new_color[5];
  94. uint8_t light_last_color[5];
  95. uint8_t light_signal_color[5];
  96. uint8_t light_wheel = 0;
  97. uint8_t light_subtype = 0;
  98. uint8_t light_device = 0;
  99. uint8_t light_power = 0;
  100. uint8_t light_update = 1;
  101. uint8_t light_wakeup_active = 0;
  102. uint8_t light_wakeup_dimmer = 0;
  103. uint16_t light_wakeup_counter = 0;
  104. uint8_t light_fixed_color_index = 1;
  105. unsigned long strip_timer_counter = 0; // Bars and Gradient
  106. #ifdef USE_ARILUX_RF
  107. /*********************************************************************************************\
  108. * Arilux LC11 Rf support stripped from RCSwitch library
  109. \*********************************************************************************************/
  110. #define ARILUX_RF_TIME_AVOID_DUPLICATE 1000 // Milliseconds
  111. #define ARILUX_RF_MAX_CHANGES 51 // Pulses (sync + 2 x 24 bits)
  112. #define ARILUX_RF_SEPARATION_LIMIT 4300 // Microseconds
  113. #define ARILUX_RF_RECEIVE_TOLERANCE 60 // Percentage
  114. unsigned int arilux_rf_timings[ARILUX_RF_MAX_CHANGES];
  115. unsigned long arilux_rf_received_value = 0;
  116. unsigned long arilux_rf_last_received_value = 0;
  117. unsigned long arilux_rf_last_time = 0;
  118. unsigned long arilux_rf_lasttime = 0;
  119. unsigned int arilux_rf_change_count = 0;
  120. unsigned int arilux_rf_repeat_count = 0;
  121. uint8_t arilux_rf_toggle = 0;
  122. #ifndef ARDUINO_ESP8266_RELEASE_2_3_0
  123. #ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves RF misses
  124. void AriluxRfInterrupt(void) ICACHE_RAM_ATTR; // As iram is tight and it works this way too
  125. #endif // USE_WS2812_DMA
  126. #endif // ARDUINO_ESP8266_RELEASE_2_3_0
  127. void AriluxRfInterrupt(void)
  128. {
  129. unsigned long time = micros();
  130. unsigned int duration = time - arilux_rf_lasttime;
  131. if (duration > ARILUX_RF_SEPARATION_LIMIT) {
  132. if (abs(duration - arilux_rf_timings[0]) < 200) {
  133. arilux_rf_repeat_count++;
  134. if (arilux_rf_repeat_count == 2) {
  135. unsigned long code = 0;
  136. const unsigned int delay = arilux_rf_timings[0] / 31;
  137. const unsigned int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100;
  138. for (unsigned int i = 1; i < arilux_rf_change_count -1; i += 2) {
  139. code <<= 1;
  140. if (abs(arilux_rf_timings[i] - (delay *3)) < delayTolerance && abs(arilux_rf_timings[i +1] - delay) < delayTolerance) {
  141. code |= 1;
  142. }
  143. }
  144. if (arilux_rf_change_count > 49) { // Need 1 sync bit and 24 data bits
  145. arilux_rf_received_value = code;
  146. }
  147. arilux_rf_repeat_count = 0;
  148. }
  149. }
  150. arilux_rf_change_count = 0;
  151. }
  152. if (arilux_rf_change_count >= ARILUX_RF_MAX_CHANGES) {
  153. arilux_rf_change_count = 0;
  154. arilux_rf_repeat_count = 0;
  155. }
  156. arilux_rf_timings[arilux_rf_change_count++] = duration;
  157. arilux_rf_lasttime = time;
  158. }
  159. void AriluxRfHandler(void)
  160. {
  161. unsigned long now = millis();
  162. if (arilux_rf_received_value && !((arilux_rf_received_value == arilux_rf_last_received_value) && (now - arilux_rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) {
  163. arilux_rf_last_received_value = arilux_rf_received_value;
  164. arilux_rf_last_time = now;
  165. uint16_t hostcode = arilux_rf_received_value >> 8 & 0xFFFF;
  166. if (Settings.rf_code[1][6] == Settings.rf_code[1][7]) {
  167. Settings.rf_code[1][6] = hostcode >> 8 & 0xFF;
  168. Settings.rf_code[1][7] = hostcode & 0xFF;
  169. }
  170. uint16_t stored_hostcode = Settings.rf_code[1][6] << 8 | Settings.rf_code[1][7];
  171. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, arilux_rf_received_value);
  172. AddLog(LOG_LEVEL_DEBUG);
  173. if (hostcode == stored_hostcode) {
  174. char command[33];
  175. char value = '-';
  176. command[0] = '\0';
  177. uint8_t keycode = arilux_rf_received_value & 0xFF;
  178. switch (keycode) {
  179. case 1: // Power On
  180. case 3: // Power Off
  181. snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0);
  182. break;
  183. case 2: // Toggle
  184. arilux_rf_toggle++;
  185. arilux_rf_toggle &= 0x3;
  186. snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + arilux_rf_toggle);
  187. break;
  188. case 4: // Speed +
  189. value = '+';
  190. case 7: // Speed -
  191. snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value);
  192. break;
  193. case 5: // Scheme +
  194. value = '+';
  195. case 8: // Scheme -
  196. snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value);
  197. break;
  198. case 6: // Dimmer +
  199. value = '+';
  200. case 9: // Dimmer -
  201. snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value);
  202. break;
  203. default: {
  204. if ((keycode >= 10) && (keycode <= 21)) {
  205. snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9);
  206. }
  207. }
  208. }
  209. if (strlen(command)) {
  210. ExecuteCommand(command, SRC_LIGHT);
  211. }
  212. }
  213. }
  214. arilux_rf_received_value = 0;
  215. }
  216. void AriluxRfInit(void)
  217. {
  218. if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_LED2] < 99)) {
  219. if (Settings.last_module != Settings.module) {
  220. Settings.rf_code[1][6] = 0;
  221. Settings.rf_code[1][7] = 0;
  222. Settings.last_module = Settings.module;
  223. }
  224. arilux_rf_received_value = 0;
  225. digitalWrite(pin[GPIO_LED2], !bitRead(led_inverted, 1)); // Turn on RF
  226. attachInterrupt(pin[GPIO_ARIRFRCV], AriluxRfInterrupt, CHANGE);
  227. }
  228. }
  229. void AriluxRfDisable(void)
  230. {
  231. if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_LED2] < 99)) {
  232. detachInterrupt(pin[GPIO_ARIRFRCV]);
  233. digitalWrite(pin[GPIO_LED2], bitRead(led_inverted, 1)); // Turn off RF
  234. }
  235. }
  236. #endif // USE_ARILUX_RF
  237. /*********************************************************************************************\
  238. * Sonoff B1 and AiLight inspired by OpenLight https://github.com/icamgo/noduino-sdk
  239. \*********************************************************************************************/
  240. extern "C" {
  241. void os_delay_us(unsigned int);
  242. }
  243. uint8_t light_pdi_pin;
  244. uint8_t light_pdcki_pin;
  245. void LightDiPulse(uint8_t times)
  246. {
  247. for (uint8_t i = 0; i < times; i++) {
  248. digitalWrite(light_pdi_pin, HIGH);
  249. digitalWrite(light_pdi_pin, LOW);
  250. }
  251. }
  252. void LightDckiPulse(uint8_t times)
  253. {
  254. for (uint8_t i = 0; i < times; i++) {
  255. digitalWrite(light_pdcki_pin, HIGH);
  256. digitalWrite(light_pdcki_pin, LOW);
  257. }
  258. }
  259. void LightMy92x1Write(uint8_t data)
  260. {
  261. for (uint8_t i = 0; i < 4; i++) { // Send 8bit Data
  262. digitalWrite(light_pdcki_pin, LOW);
  263. digitalWrite(light_pdi_pin, (data & 0x80));
  264. digitalWrite(light_pdcki_pin, HIGH);
  265. data = data << 1;
  266. digitalWrite(light_pdi_pin, (data & 0x80));
  267. digitalWrite(light_pdcki_pin, LOW);
  268. digitalWrite(light_pdi_pin, LOW);
  269. data = data << 1;
  270. }
  271. }
  272. void LightMy92x1Init(void)
  273. {
  274. uint8_t chips = 1; // 1 (AiLight)
  275. if (LT_RGBWC == light_type) {
  276. chips = 2; // 2 (Sonoff B1)
  277. }
  278. LightDckiPulse(chips * 32); // Clear all duty register
  279. os_delay_us(12); // TStop > 12us.
  280. // Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
  281. // pulse's rising edge convert to command mode.
  282. LightDiPulse(12);
  283. os_delay_us(12); // Delay >12us, begin send CMD data
  284. for (uint8_t n = 0; n < chips; n++) { // Send CMD data
  285. LightMy92x1Write(0x18); // ONE_SHOT_DISABLE, REACTION_FAST, BIT_WIDTH_8, FREQUENCY_DIVIDE_1, SCATTER_APDM
  286. }
  287. os_delay_us(12); // TStart > 12us. Delay 12 us.
  288. // Send 16 DI pulse, at 14 pulse's falling edge store CMD data, and
  289. // at 16 pulse's falling edge convert to duty mode.
  290. LightDiPulse(16);
  291. os_delay_us(12); // TStop > 12us.
  292. }
  293. void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c)
  294. {
  295. uint8_t channels[2] = { 4, 6 };
  296. uint8_t didx = 0; // 0 (AiLight)
  297. if (LT_RGBWC == light_type) {
  298. didx = 1; // 1 (Sonoff B1)
  299. }
  300. uint8_t duty[2][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 }, // Definition for RGBW channels
  301. { duty_w, duty_c, 0, duty_g, duty_r, duty_b }}; // Definition for RGBWC channels
  302. os_delay_us(12); // TStop > 12us.
  303. for (uint8_t channel = 0; channel < channels[didx]; channel++) {
  304. LightMy92x1Write(duty[didx][channel]); // Send 8bit Data
  305. }
  306. os_delay_us(12); // TStart > 12us. Ready for send DI pulse.
  307. LightDiPulse(8); // Send 8 DI pulse. After 8 pulse falling edge, store old data.
  308. os_delay_us(12); // TStop > 12us.
  309. }
  310. /********************************************************************************************/
  311. void LightInit(void)
  312. {
  313. uint8_t max_scheme = LS_MAX -1;
  314. light_device = devices_present;
  315. light_subtype = light_type &7; // Always 0 - 7
  316. if (LST_SINGLE == light_subtype) {
  317. Settings.light_color[0] = 255; // One channel only supports Dimmer but needs max color
  318. }
  319. if (light_type < LT_PWM6) { // PWM
  320. for (byte i = 0; i < light_type; i++) {
  321. Settings.pwm_value[i] = 0; // Disable direct PWM control
  322. if (pin[GPIO_PWM1 +i] < 99) {
  323. pinMode(pin[GPIO_PWM1 +i], OUTPUT);
  324. }
  325. }
  326. if (SONOFF_LED == Settings.module) { // Fix Sonoff Led instabilities
  327. if (!my_module.gp.io[4]) {
  328. pinMode(4, OUTPUT); // Stop floating outputs
  329. digitalWrite(4, LOW);
  330. }
  331. if (!my_module.gp.io[5]) {
  332. pinMode(5, OUTPUT); // Stop floating outputs
  333. digitalWrite(5, LOW);
  334. }
  335. if (!my_module.gp.io[14]) {
  336. pinMode(14, OUTPUT); // Stop floating outputs
  337. digitalWrite(14, LOW);
  338. }
  339. }
  340. if (pin[GPIO_ARIRFRCV] < 99) {
  341. if (pin[GPIO_LED2] < 99) {
  342. digitalWrite(pin[GPIO_LED2], bitRead(led_inverted, 1)); // Turn off RF
  343. }
  344. }
  345. }
  346. #ifdef USE_WS2812 // ************************************************************************
  347. else if (LT_WS2812 == light_type) {
  348. #if (USE_WS2812_CTYPE > NEO_3LED)
  349. light_subtype++; // from RGB to RGBW
  350. #endif
  351. Ws2812Init();
  352. max_scheme = LS_MAX + WS2812_SCHEMES;
  353. }
  354. #endif // USE_WS2812 ************************************************************************
  355. else {
  356. light_pdi_pin = pin[GPIO_DI];
  357. light_pdcki_pin = pin[GPIO_DCKI];
  358. pinMode(light_pdi_pin, OUTPUT);
  359. pinMode(light_pdcki_pin, OUTPUT);
  360. digitalWrite(light_pdi_pin, LOW);
  361. digitalWrite(light_pdcki_pin, LOW);
  362. LightMy92x1Init();
  363. }
  364. if (light_subtype < LST_RGB) {
  365. max_scheme = LS_POWER;
  366. }
  367. if ((LS_WAKEUP == Settings.light_scheme) || (Settings.light_scheme > max_scheme)) {
  368. Settings.light_scheme = LS_POWER;
  369. }
  370. light_power = 0;
  371. light_update = 1;
  372. light_wakeup_active = 0;
  373. }
  374. void LightSetColorTemp(uint16_t ct)
  375. {
  376. /* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
  377. *
  378. * ct = 153 = 2000K = Warm = CCWW = 00FF
  379. * ct = 500 = 6500K = Cold = CCWW = FF00
  380. */
  381. uint16_t my_ct = ct - 153;
  382. if (my_ct > 347) {
  383. my_ct = 347;
  384. }
  385. uint16_t icold = (100 * (347 - my_ct)) / 136;
  386. uint16_t iwarm = (100 * my_ct) / 136;
  387. if (PHILIPS == Settings.module) {
  388. // Xiaomi Philips bulbs follow a different scheme:
  389. // channel 0=intensity, channel2=temperature
  390. Settings.light_color[1] = (uint8_t)icold;
  391. } else
  392. if (LST_RGBWC == light_subtype) {
  393. Settings.light_color[0] = 0;
  394. Settings.light_color[1] = 0;
  395. Settings.light_color[2] = 0;
  396. Settings.light_color[3] = (uint8_t)icold;
  397. Settings.light_color[4] = (uint8_t)iwarm;
  398. } else {
  399. Settings.light_color[0] = (uint8_t)icold;
  400. Settings.light_color[1] = (uint8_t)iwarm;
  401. }
  402. }
  403. uint16_t LightGetColorTemp(void)
  404. {
  405. uint8_t ct_idx = 0;
  406. if (LST_RGBWC == light_subtype) {
  407. ct_idx = 3;
  408. }
  409. uint16_t my_ct = Settings.light_color[ct_idx +1];
  410. if (my_ct > 0) {
  411. return ((my_ct * 136) / 100) + 154;
  412. } else {
  413. my_ct = Settings.light_color[ct_idx];
  414. return 499 - ((my_ct * 136) / 100);
  415. }
  416. }
  417. void LightSetDimmer(uint8_t myDimmer)
  418. {
  419. float temp;
  420. if (PHILIPS == Settings.module) {
  421. // Xiaomi Philips bulbs use two PWM channels with a different scheme:
  422. float dimmer = 100 / (float)myDimmer;
  423. temp = (float)Settings.light_color[0] / dimmer; // channel 1 is intensity
  424. light_current_color[0] = (uint8_t)temp;
  425. temp = (float)Settings.light_color[1]; // channel 2 is temperature
  426. light_current_color[1] = (uint8_t)temp;
  427. return;
  428. }
  429. if (LT_PWM1 == light_type) {
  430. Settings.light_color[0] = 255; // One PWM channel only supports Dimmer but needs max color
  431. }
  432. float dimmer = 100 / (float)myDimmer;
  433. for (byte i = 0; i < light_subtype; i++) {
  434. if (Settings.flag.light_signal) {
  435. temp = (float)light_signal_color[i] / dimmer;
  436. } else {
  437. temp = (float)Settings.light_color[i] / dimmer;
  438. }
  439. light_current_color[i] = (uint8_t)temp;
  440. }
  441. }
  442. void LightSetColor(void)
  443. {
  444. uint8_t highest = 0;
  445. for (byte i = 0; i < light_subtype; i++) {
  446. if (highest < light_current_color[i]) {
  447. highest = light_current_color[i];
  448. }
  449. }
  450. float mDim = (float)highest / 2.55;
  451. Settings.light_dimmer = (uint8_t)mDim;
  452. float dimmer = 100 / mDim;
  453. for (byte i = 0; i < light_subtype; i++) {
  454. float temp = (float)light_current_color[i] * dimmer;
  455. Settings.light_color[i] = (uint8_t)temp;
  456. }
  457. }
  458. void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value)
  459. {
  460. /* lo - below lo is green
  461. hi - above hi is red
  462. */
  463. if (Settings.flag.light_signal) {
  464. uint16_t signal = 0;
  465. if (value > lo) {
  466. signal = (value - lo) * 10 / ((hi - lo) * 10 / 256);
  467. if (signal > 255) {
  468. signal = 255;
  469. }
  470. }
  471. // snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "Light signal %d"), signal);
  472. // AddLog(LOG_LEVEL_DEBUG);
  473. light_signal_color[0] = signal;
  474. light_signal_color[1] = 255 - signal;
  475. light_signal_color[2] = 0;
  476. light_signal_color[3] = 0;
  477. light_signal_color[4] = 0;
  478. Settings.light_scheme = 0;
  479. if (!Settings.light_dimmer) {
  480. Settings.light_dimmer = 20;
  481. }
  482. }
  483. }
  484. char* LightGetColor(uint8_t type, char* scolor)
  485. {
  486. LightSetDimmer(Settings.light_dimmer);
  487. scolor[0] = '\0';
  488. for (byte i = 0; i < light_subtype; i++) {
  489. if (!type && Settings.flag.decimal_text) {
  490. snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", light_current_color[i]);
  491. } else {
  492. snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, light_current_color[i]);
  493. }
  494. }
  495. return scolor;
  496. }
  497. void LightPowerOn(void)
  498. {
  499. if (Settings.light_dimmer && !(light_power)) {
  500. ExecuteCommandPower(light_device, POWER_ON, SRC_LIGHT);
  501. }
  502. }
  503. void LightState(uint8_t append)
  504. {
  505. char scolor[25];
  506. char scommand[33];
  507. float hsb[3];
  508. int16_t h,s,b;
  509. if (append) {
  510. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data);
  511. } else {
  512. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{"));
  513. }
  514. GetPowerDevice(scommand, light_device, sizeof(scommand), Settings.flag.device_index_enable);
  515. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"),
  516. mqtt_data, scommand, GetStateText(light_power), Settings.light_dimmer);
  517. if (light_subtype > LST_SINGLE) {
  518. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_COLOR "\":\"%s\""), mqtt_data, LightGetColor(0, scolor));
  519. // Add status for HSB
  520. LightGetHsb(&hsb[0],&hsb[1],&hsb[2], false);
  521. // Scale these percentages up to the numbers expected by the client
  522. h = round(hsb[0] * 360);
  523. s = round(hsb[1] * 100);
  524. b = round(hsb[2] * 100);
  525. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_HSBCOLOR "\":\"%d,%d,%d\""), mqtt_data, h,s,b);
  526. // Add status for each channel
  527. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_CHANNEL "\":[" ), mqtt_data);
  528. for (byte i = 0; i < light_subtype; i++) {
  529. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s%d" ), mqtt_data, (i > 0 ? "," : ""), light_current_color[i] * 100 / 255);
  530. }
  531. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s]" ), mqtt_data);
  532. }
  533. if ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype)) {
  534. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_COLORTEMPERATURE "\":%d"), mqtt_data, LightGetColorTemp());
  535. }
  536. if (append) {
  537. if (light_subtype >= LST_RGB) {
  538. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_SCHEME "\":%d"), mqtt_data, Settings.light_scheme);
  539. }
  540. if (LT_WS2812 == light_type) {
  541. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_WIDTH "\":%d"), mqtt_data, Settings.light_width);
  542. }
  543. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_CMND_FADE "\":\"%s\",\"" D_CMND_SPEED "\":%d,\"" D_CMND_LEDTABLE "\":\"%s\""),
  544. mqtt_data, GetStateText(Settings.light_fade), Settings.light_speed, GetStateText(Settings.light_correction));
  545. } else {
  546. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  547. }
  548. }
  549. void LightPreparePower(void)
  550. {
  551. if (Settings.light_dimmer && !(light_power)) {
  552. if (!Settings.flag.not_power_linked) {
  553. ExecuteCommandPower(light_device, POWER_ON_NO_STATE, SRC_LIGHT);
  554. }
  555. }
  556. else if (!Settings.light_dimmer && light_power) {
  557. ExecuteCommandPower(light_device, POWER_OFF_NO_STATE, SRC_LIGHT);
  558. }
  559. #ifdef USE_DOMOTICZ
  560. DomoticzUpdatePowerState(light_device);
  561. #endif // USE_DOMOTICZ
  562. if (Settings.flag3.hass_tele_on_power) {
  563. mqtt_data[0] = '\0';
  564. MqttShowState();
  565. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
  566. }
  567. LightState(0);
  568. }
  569. void LightFade(void)
  570. {
  571. if (0 == Settings.light_fade) {
  572. for (byte i = 0; i < light_subtype; i++) {
  573. light_new_color[i] = light_current_color[i];
  574. }
  575. } else {
  576. uint8_t shift = Settings.light_speed;
  577. if (Settings.light_speed > 6) {
  578. shift = (strip_timer_counter % (Settings.light_speed -6)) ? 0 : 8;
  579. }
  580. if (shift) {
  581. for (byte i = 0; i < light_subtype; i++) {
  582. if (light_new_color[i] != light_current_color[i]) {
  583. if (light_new_color[i] < light_current_color[i]) {
  584. light_new_color[i] += ((light_current_color[i] - light_new_color[i]) >> shift) +1;
  585. }
  586. if (light_new_color[i] > light_current_color[i]) {
  587. light_new_color[i] -= ((light_new_color[i] - light_current_color[i]) >> shift) +1;
  588. }
  589. }
  590. }
  591. }
  592. }
  593. }
  594. void LightWheel(uint8_t wheel_pos)
  595. {
  596. wheel_pos = 255 - wheel_pos;
  597. if (wheel_pos < 85) {
  598. light_entry_color[0] = 255 - wheel_pos * 3;
  599. light_entry_color[1] = 0;
  600. light_entry_color[2] = wheel_pos * 3;
  601. } else if (wheel_pos < 170) {
  602. wheel_pos -= 85;
  603. light_entry_color[0] = 0;
  604. light_entry_color[1] = wheel_pos * 3;
  605. light_entry_color[2] = 255 - wheel_pos * 3;
  606. } else {
  607. wheel_pos -= 170;
  608. light_entry_color[0] = wheel_pos * 3;
  609. light_entry_color[1] = 255 - wheel_pos * 3;
  610. light_entry_color[2] = 0;
  611. }
  612. light_entry_color[3] = 0;
  613. light_entry_color[4] = 0;
  614. float dimmer = 100 / (float)Settings.light_dimmer;
  615. for (byte i = 0; i < LST_RGB; i++) {
  616. float temp = (float)light_entry_color[i] / dimmer;
  617. light_entry_color[i] = (uint8_t)temp;
  618. }
  619. }
  620. void LightCycleColor(int8_t direction)
  621. {
  622. if (strip_timer_counter % (Settings.light_speed * 2)) {
  623. return;
  624. }
  625. light_wheel += direction;
  626. LightWheel(light_wheel);
  627. memcpy(light_new_color, light_entry_color, sizeof(light_new_color));
  628. }
  629. void LightRandomColor(void)
  630. {
  631. uint8_t light_update = 0;
  632. for (byte i = 0; i < LST_RGB; i++) {
  633. if (light_new_color[i] != light_current_color[i]) {
  634. light_update = 1;
  635. }
  636. }
  637. if (!light_update) {
  638. light_wheel = random(255);
  639. LightWheel(light_wheel);
  640. memcpy(light_current_color, light_entry_color, sizeof(light_current_color));
  641. }
  642. LightFade();
  643. }
  644. void LightSetPower(void)
  645. {
  646. // light_power = XdrvMailbox.index;
  647. light_power = bitRead(XdrvMailbox.index, light_device -1);
  648. if (light_wakeup_active) {
  649. light_wakeup_active--;
  650. }
  651. if (light_power) {
  652. light_update = 1;
  653. }
  654. LightAnimate();
  655. }
  656. void LightAnimate(void)
  657. {
  658. uint8_t cur_col[5];
  659. uint16_t light_still_on = 0;
  660. strip_timer_counter++;
  661. if (!light_power) { // Power Off
  662. sleep = Settings.sleep;
  663. strip_timer_counter = 0;
  664. for (byte i = 0; i < light_subtype; i++) {
  665. light_still_on += light_new_color[i];
  666. }
  667. if (light_still_on && Settings.light_fade && (Settings.light_scheme < LS_MAX)) {
  668. uint8_t speed = Settings.light_speed;
  669. if (speed > 6) {
  670. speed = 6;
  671. }
  672. for (byte i = 0; i < light_subtype; i++) {
  673. if (light_new_color[i] > 0) {
  674. light_new_color[i] -= (light_new_color[i] >> speed) +1;
  675. }
  676. }
  677. } else {
  678. for (byte i = 0; i < light_subtype; i++) {
  679. light_new_color[i] = 0;
  680. }
  681. }
  682. }
  683. else {
  684. sleep = 0;
  685. switch (Settings.light_scheme) {
  686. case LS_POWER:
  687. LightSetDimmer(Settings.light_dimmer);
  688. LightFade();
  689. break;
  690. case LS_WAKEUP:
  691. if (2 == light_wakeup_active) {
  692. light_wakeup_active = 1;
  693. for (byte i = 0; i < light_subtype; i++) {
  694. light_new_color[i] = 0;
  695. }
  696. light_wakeup_counter = 0;
  697. light_wakeup_dimmer = 0;
  698. }
  699. light_wakeup_counter++;
  700. if (light_wakeup_counter > ((Settings.light_wakeup * STATES) / Settings.light_dimmer)) {
  701. light_wakeup_counter = 0;
  702. light_wakeup_dimmer++;
  703. if (light_wakeup_dimmer <= Settings.light_dimmer) {
  704. LightSetDimmer(light_wakeup_dimmer);
  705. for (byte i = 0; i < light_subtype; i++) {
  706. light_new_color[i] = light_current_color[i];
  707. }
  708. } else {
  709. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"}"));
  710. MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP));
  711. light_wakeup_active = 0;
  712. Settings.light_scheme = LS_POWER;
  713. }
  714. }
  715. break;
  716. case LS_CYCLEUP:
  717. LightCycleColor(1);
  718. break;
  719. case LS_CYCLEDN:
  720. LightCycleColor(-1);
  721. break;
  722. case LS_RANDOM:
  723. LightRandomColor();
  724. break;
  725. #ifdef USE_WS2812 // ************************************************************************
  726. default:
  727. if (LT_WS2812 == light_type) {
  728. Ws2812ShowScheme(Settings.light_scheme -LS_MAX);
  729. }
  730. #endif // USE_WS2812 ************************************************************************
  731. }
  732. }
  733. if ((Settings.light_scheme < LS_MAX) || !light_power) {
  734. for (byte i = 0; i < light_subtype; i++) {
  735. if (light_last_color[i] != light_new_color[i]) {
  736. light_update = 1;
  737. }
  738. }
  739. if (light_update) {
  740. light_update = 0;
  741. for (byte i = 0; i < light_subtype; i++) {
  742. light_last_color[i] = light_new_color[i];
  743. cur_col[i] = light_last_color[i]*Settings.rgbwwTable[i]/255;
  744. cur_col[i] = (Settings.light_correction) ? ledTable[cur_col[i]] : cur_col[i];
  745. if (light_type < LT_PWM6) {
  746. if (pin[GPIO_PWM1 +i] < 99) {
  747. if (cur_col[i] > 0xFC) {
  748. cur_col[i] = 0xFC; // Fix unwanted blinking and PWM watchdog errors for values close to pwm_range (H801, Arilux and BN-SZ01)
  749. }
  750. uint16_t curcol = cur_col[i] * (Settings.pwm_range / 255);
  751. // snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION "Cur_Col%d %d, CurCol %d"), i, cur_col[i], curcol);
  752. // AddLog(LOG_LEVEL_DEBUG);
  753. analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - curcol : curcol);
  754. }
  755. }
  756. }
  757. XdrvMailbox.index = light_device;
  758. XdrvMailbox.data = (char*)cur_col;
  759. XdrvMailbox.data_len = sizeof(cur_col);
  760. if (XdrvCall(FUNC_SET_CHANNELS)) {
  761. // Serviced
  762. }
  763. #ifdef USE_WS2812 // ************************************************************************
  764. else if (LT_WS2812 == light_type) {
  765. Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
  766. }
  767. #endif // USE_ES2812 ************************************************************************
  768. else if (light_type > LT_WS2812) {
  769. LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
  770. }
  771. }
  772. }
  773. }
  774. /*********************************************************************************************\
  775. * Hue support
  776. \*********************************************************************************************/
  777. float light_hue = 0.0;
  778. float light_saturation = 0.0;
  779. float light_brightness = 0.0;
  780. void LightRgbToHsb(void)
  781. {
  782. LightSetDimmer(Settings.light_dimmer);
  783. // convert colors to float between (0.0 - 1.0)
  784. float r = light_current_color[0] / 255.0f;
  785. float g = light_current_color[1] / 255.0f;
  786. float b = light_current_color[2] / 255.0f;
  787. float max = (r > g && r > b) ? r : (g > b) ? g : b;
  788. float min = (r < g && r < b) ? r : (g < b) ? g : b;
  789. float d = max - min;
  790. light_hue = 0.0;
  791. light_brightness = max;
  792. light_saturation = (0.0f == light_brightness) ? 0 : (d / light_brightness);
  793. if (d != 0.0f)
  794. {
  795. if (r == max) {
  796. light_hue = (g - b) / d + (g < b ? 6.0f : 0.0f);
  797. } else if (g == max) {
  798. light_hue = (b - r) / d + 2.0f;
  799. } else {
  800. light_hue = (r - g) / d + 4.0f;
  801. }
  802. light_hue /= 6.0f;
  803. }
  804. }
  805. void LightHsbToRgb(void)
  806. {
  807. float r;
  808. float g;
  809. float b;
  810. float h = light_hue;
  811. float s = light_saturation;
  812. float v = light_brightness;
  813. if (0.0f == light_saturation) {
  814. r = g = b = v; // Achromatic or black
  815. } else {
  816. if (h < 0.0f) {
  817. h += 1.0f;
  818. }
  819. else if (h >= 1.0f) {
  820. h -= 1.0f;
  821. }
  822. h *= 6.0f;
  823. int i = (int)h;
  824. float f = h - i;
  825. float q = v * (1.0f - s * f);
  826. float p = v * (1.0f - s);
  827. float t = v * (1.0f - s * (1.0f - f));
  828. switch (i) {
  829. case 0:
  830. r = v;
  831. g = t;
  832. b = p;
  833. break;
  834. case 1:
  835. r = q;
  836. g = v;
  837. b = p;
  838. break;
  839. case 2:
  840. r = p;
  841. g = v;
  842. b = t;
  843. break;
  844. case 3:
  845. r = p;
  846. g = q;
  847. b = v;
  848. break;
  849. case 4:
  850. r = t;
  851. g = p;
  852. b = v;
  853. break;
  854. default:
  855. r = v;
  856. g = p;
  857. b = q;
  858. break;
  859. }
  860. }
  861. light_current_color[0] = (uint8_t)(r * 255.0f);
  862. light_current_color[1] = (uint8_t)(g * 255.0f);
  863. light_current_color[2] = (uint8_t)(b * 255.0f);
  864. light_current_color[3] = 0;
  865. light_current_color[4] = 0;
  866. }
  867. /********************************************************************************************/
  868. void LightGetHsb(float *hue, float *sat, float *bri, bool gotct)
  869. {
  870. if (light_subtype > LST_COLDWARM && !gotct) {
  871. LightRgbToHsb();
  872. *hue = light_hue;
  873. *sat = light_saturation;
  874. *bri = light_brightness;
  875. } else {
  876. *hue = 0;
  877. *sat = 0;
  878. *bri = (0.01f * (float)Settings.light_dimmer);
  879. }
  880. }
  881. void LightSetHsb(float hue, float sat, float bri, uint16_t ct, bool gotct)
  882. {
  883. if (light_subtype > LST_COLDWARM) {
  884. if ((LST_RGBWC == light_subtype) && (gotct)) {
  885. uint8_t tmp = (uint8_t)(bri * 100);
  886. Settings.light_dimmer = tmp;
  887. if (ct > 0) {
  888. LightSetColorTemp(ct);
  889. }
  890. } else {
  891. light_hue = hue;
  892. light_saturation = sat;
  893. light_brightness = bri;
  894. LightHsbToRgb();
  895. LightSetColor();
  896. }
  897. LightPreparePower();
  898. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
  899. } else {
  900. uint8_t tmp = (uint8_t)(bri * 100);
  901. Settings.light_dimmer = tmp;
  902. if (LST_COLDWARM == light_subtype) {
  903. if (ct > 0) {
  904. LightSetColorTemp(ct);
  905. }
  906. LightPreparePower();
  907. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
  908. } else {
  909. LightPreparePower();
  910. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
  911. }
  912. }
  913. }
  914. /*********************************************************************************************\
  915. * Commands
  916. \*********************************************************************************************/
  917. boolean LightColorEntry(char *buffer, uint8_t buffer_length)
  918. {
  919. char scolor[10];
  920. char *p;
  921. char *str;
  922. uint8_t entry_type = 0; // Invalid
  923. uint8_t value = light_fixed_color_index;
  924. if (buffer[0] == '#') { // Optional hexadecimal entry
  925. buffer++;
  926. buffer_length--;
  927. }
  928. if (light_subtype >= LST_RGB) {
  929. char option = (1 == buffer_length) ? buffer[0] : '\0';
  930. if (('+' == option) && (light_fixed_color_index < MAX_FIXED_COLOR)) {
  931. value++;
  932. }
  933. else if (('-' == option) && (light_fixed_color_index > 1)) {
  934. value--;
  935. } else {
  936. value = atoi(buffer);
  937. }
  938. }
  939. memset(&light_entry_color, 0x00, sizeof(light_entry_color));
  940. if (strstr(buffer, ",")) { // Decimal entry
  941. int8_t i = 0;
  942. for (str = strtok_r(buffer, ",", &p); str && i < 6; str = strtok_r(NULL, ",", &p)) {
  943. if (i < 5) {
  944. light_entry_color[i++] = atoi(str);
  945. }
  946. }
  947. entry_type = 2; // Decimal
  948. }
  949. else if (((2 * light_subtype) == buffer_length) || (buffer_length > 3)) { // Hexadecimal entry
  950. for (byte i = 0; i < buffer_length / 2; i++) {
  951. strlcpy(scolor, buffer + (i *2), 3);
  952. light_entry_color[i] = (uint8_t)strtol(scolor, &p, 16);
  953. }
  954. entry_type = 1; // Hexadecimal
  955. }
  956. else if ((light_subtype >= LST_RGB) && (value > 0) && (value <= MAX_FIXED_COLOR)) {
  957. light_fixed_color_index = value;
  958. memcpy_P(&light_entry_color, &kFixedColor[value -1], 3);
  959. entry_type = 1; // Hexadecimal
  960. }
  961. else if ((value > 199) && (value <= 199 + MAX_FIXED_COLD_WARM)) {
  962. if (LST_RGBW == light_subtype) {
  963. memcpy_P(&light_entry_color[3], &kFixedWhite[value -200], 1);
  964. entry_type = 1; // Hexadecimal
  965. }
  966. else if (LST_COLDWARM == light_subtype) {
  967. memcpy_P(&light_entry_color, &kFixedColdWarm[value -200], 2);
  968. entry_type = 1; // Hexadecimal
  969. }
  970. else if (LST_RGBWC == light_subtype) {
  971. memcpy_P(&light_entry_color[3], &kFixedColdWarm[value -200], 2);
  972. entry_type = 1; // Hexadecimal
  973. }
  974. }
  975. if (entry_type) {
  976. Settings.flag.decimal_text = entry_type -1;
  977. }
  978. return (entry_type);
  979. }
  980. /********************************************************************************************/
  981. boolean LightCommand(void)
  982. {
  983. char command [CMDSZ];
  984. boolean serviced = true;
  985. boolean coldim = false;
  986. boolean valid_entry = false;
  987. char scolor[25];
  988. char option = (1 == XdrvMailbox.data_len) ? XdrvMailbox.data[0] : '\0';
  989. int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kLightCommands);
  990. if (-1 == command_code) {
  991. serviced = false; // Unknown command
  992. }
  993. else if (((CMND_COLOR == command_code) && (light_subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) ||
  994. ((CMND_WHITE == command_code) && (light_subtype == LST_RGBW) && (XdrvMailbox.index == 1))) {
  995. if (CMND_WHITE == command_code) {
  996. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
  997. snprintf_P(scolor, sizeof(scolor), PSTR("0,0,0,%d"), XdrvMailbox.payload * 255 / 100);
  998. XdrvMailbox.data = scolor;
  999. XdrvMailbox.data_len = strlen(scolor);
  1000. } else {
  1001. XdrvMailbox.data_len = 0;
  1002. }
  1003. }
  1004. if (XdrvMailbox.data_len > 0) {
  1005. valid_entry = LightColorEntry(XdrvMailbox.data, XdrvMailbox.data_len);
  1006. if (valid_entry) {
  1007. if (XdrvMailbox.index <= 2) { // Color(1), 2
  1008. memcpy(light_current_color, light_entry_color, sizeof(light_current_color));
  1009. uint8_t dimmer = Settings.light_dimmer;
  1010. LightSetColor();
  1011. if (2 == XdrvMailbox.index) {
  1012. Settings.light_dimmer = dimmer;
  1013. }
  1014. Settings.light_scheme = 0;
  1015. coldim = true;
  1016. } else { // Color3, 4, 5 and 6
  1017. for (byte i = 0; i < LST_RGB; i++) {
  1018. Settings.ws_color[XdrvMailbox.index -3][i] = light_entry_color[i];
  1019. }
  1020. }
  1021. }
  1022. }
  1023. if (!valid_entry && (XdrvMailbox.index <= 2)) {
  1024. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, LightGetColor(0, scolor));
  1025. }
  1026. if (XdrvMailbox.index >= 3) {
  1027. scolor[0] = '\0';
  1028. for (byte i = 0; i < LST_RGB; i++) {
  1029. if (Settings.flag.decimal_text) {
  1030. snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.ws_color[XdrvMailbox.index -3][i]);
  1031. } else {
  1032. snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, Settings.ws_color[XdrvMailbox.index -3][i]);
  1033. }
  1034. }
  1035. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, XdrvMailbox.index, scolor);
  1036. }
  1037. }
  1038. else if ((CMND_CHANNEL == command_code) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= light_subtype ) ) {
  1039. // Set "Channel" directly - this allows Color and Direct PWM control to coexist
  1040. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
  1041. light_current_color[XdrvMailbox.index-1] = XdrvMailbox.payload * 255 / 100;
  1042. LightSetColor();
  1043. coldim = true;
  1044. }
  1045. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, XdrvMailbox.index, light_current_color[XdrvMailbox.index -1] * 100 / 255);
  1046. }
  1047. else if ((CMND_HSBCOLOR == command_code) && ( light_subtype >= LST_RGB)) {
  1048. bool validHSB = (XdrvMailbox.data_len > 0);
  1049. if (validHSB) {
  1050. uint16_t HSB[3];
  1051. if (strstr(XdrvMailbox.data, ",")) { // Command with 3 comma separated parameters, Hue (0<H<360), Saturation (0<S<100) AND Brightness (0<B<100)
  1052. for (int i = 0; i < 3; i++) {
  1053. char *substr;
  1054. if (0 == i) {
  1055. substr = strtok(XdrvMailbox.data, ",");
  1056. } else {
  1057. substr = strtok(NULL, ",");
  1058. }
  1059. if (substr != NULL) {
  1060. HSB[i] = atoi(substr);
  1061. } else {
  1062. validHSB = false;
  1063. }
  1064. }
  1065. } else { // Command with only 1 parameter, Hue (0<H<360), Saturation (0<S<100) OR Brightness (0<B<100)
  1066. float hsb[3];
  1067. LightGetHsb(&hsb[0],&hsb[1],&hsb[2], false);
  1068. HSB[0] = round(hsb[0] * 360);
  1069. HSB[1] = round(hsb[1] * 100);
  1070. HSB[2] = round(hsb[2] * 100);
  1071. if ((XdrvMailbox.index > 0) && (XdrvMailbox.index < 4)) {
  1072. HSB[XdrvMailbox.index -1] = XdrvMailbox.payload;
  1073. } else {
  1074. validHSB = false;
  1075. }
  1076. }
  1077. if (validHSB) {
  1078. // Translate to fractional elements as required by LightHsbToRgb
  1079. // Keep the results <=1 in the event someone passes something out of range.
  1080. LightSetHsb(( (HSB[0]>360) ? (HSB[0] % 360) : HSB[0] ) /360.0,
  1081. ( (HSB[1]>100) ? (HSB[1] % 100) : HSB[1] ) /100.0,
  1082. ( (HSB[2]>100) ? (HSB[2] % 100) : HSB[2] ) /100.0,
  1083. 0,
  1084. false);
  1085. }
  1086. } else {
  1087. LightState(0);
  1088. }
  1089. }
  1090. #ifdef USE_WS2812 // ***********************************************************************
  1091. else if ((CMND_LED == command_code) && (LT_WS2812 == light_type) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings.light_pixels)) {
  1092. if (XdrvMailbox.data_len > 0) {
  1093. char *p;
  1094. uint16_t idx = XdrvMailbox.index;
  1095. Ws2812ForceSuspend();
  1096. for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(NULL, " ", &p)) {
  1097. if (LightColorEntry(color, strlen(color))) {
  1098. Ws2812SetColor(idx, light_entry_color[0], light_entry_color[1], light_entry_color[2], light_entry_color[3]);
  1099. idx++;
  1100. if (idx >= Settings.light_pixels) break;
  1101. } else {
  1102. break;
  1103. }
  1104. }
  1105. Ws2812ForceUpdate();
  1106. }
  1107. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, XdrvMailbox.index, Ws2812GetColor(XdrvMailbox.index, scolor));
  1108. }
  1109. else if ((CMND_PIXELS == command_code) && (LT_WS2812 == light_type)) {
  1110. if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) {
  1111. Settings.light_pixels = XdrvMailbox.payload;
  1112. Settings.light_rotation = 0;
  1113. Ws2812Clear();
  1114. light_update = 1;
  1115. }
  1116. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_pixels);
  1117. }
  1118. else if ((CMND_ROTATION == command_code) && (LT_WS2812 == light_type)) {
  1119. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) {
  1120. Settings.light_rotation = XdrvMailbox.payload;
  1121. }
  1122. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_rotation);
  1123. }
  1124. else if ((CMND_WIDTH == command_code) && (LT_WS2812 == light_type) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
  1125. if (1 == XdrvMailbox.index) {
  1126. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
  1127. Settings.light_width = XdrvMailbox.payload;
  1128. }
  1129. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_width);
  1130. } else {
  1131. if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 32)) {
  1132. Settings.ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload;
  1133. }
  1134. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, XdrvMailbox.index, Settings.ws_width[XdrvMailbox.index -2]);
  1135. }
  1136. }
  1137. #endif // USE_WS2812 ************************************************************************
  1138. else if ((CMND_SCHEME == command_code) && (light_subtype >= LST_RGB)) {
  1139. uint8_t max_scheme = (LT_WS2812 == light_type) ? LS_MAX + WS2812_SCHEMES : LS_MAX -1;
  1140. if (('+' == option) && (Settings.light_scheme < max_scheme)) {
  1141. XdrvMailbox.payload = Settings.light_scheme + ((0 == Settings.light_scheme) ? 2 : 1); // Skip wakeup
  1142. }
  1143. else if (('-' == option) && (Settings.light_scheme > 0)) {
  1144. XdrvMailbox.payload = Settings.light_scheme - ((2 == Settings.light_scheme) ? 2 : 1); // Skip wakeup
  1145. }
  1146. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) {
  1147. Settings.light_scheme = XdrvMailbox.payload;
  1148. if (LS_WAKEUP == Settings.light_scheme) {
  1149. light_wakeup_active = 3;
  1150. }
  1151. LightPowerOn();
  1152. strip_timer_counter = 0;
  1153. // Publish state message for Hass
  1154. if (Settings.flag3.hass_tele_on_power) {
  1155. mqtt_data[0] = '\0';
  1156. MqttShowState();
  1157. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
  1158. }
  1159. }
  1160. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_scheme);
  1161. }
  1162. else if (CMND_WAKEUP == command_code) {
  1163. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
  1164. Settings.light_dimmer = XdrvMailbox.payload;
  1165. }
  1166. light_wakeup_active = 3;
  1167. Settings.light_scheme = LS_WAKEUP;
  1168. LightPowerOn();
  1169. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_STARTED);
  1170. }
  1171. else if ((CMND_COLORTEMPERATURE == command_code) && ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype))) { // ColorTemp
  1172. if (option != '\0') {
  1173. uint16_t value = LightGetColorTemp();
  1174. if ('+' == option) {
  1175. XdrvMailbox.payload = (value > 466) ? 500 : value + 34;
  1176. }
  1177. else if ('-' == option) {
  1178. XdrvMailbox.payload = (value < 187) ? 153 : value - 34;
  1179. }
  1180. }
  1181. if ((XdrvMailbox.payload >= 153) && (XdrvMailbox.payload <= 500)) { // https://developers.meethue.com/documentation/core-concepts
  1182. LightSetColorTemp(XdrvMailbox.payload);
  1183. coldim = true;
  1184. } else {
  1185. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, LightGetColorTemp());
  1186. }
  1187. }
  1188. else if (CMND_DIMMER == command_code) {
  1189. if ('+' == option) {
  1190. XdrvMailbox.payload = (Settings.light_dimmer > 89) ? 100 : Settings.light_dimmer + 10;
  1191. }
  1192. else if ('-' == option) {
  1193. XdrvMailbox.payload = (Settings.light_dimmer < 11) ? 1 : Settings.light_dimmer - 10;
  1194. }
  1195. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
  1196. Settings.light_dimmer = XdrvMailbox.payload;
  1197. light_update = 1;
  1198. coldim = true;
  1199. } else {
  1200. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_dimmer);
  1201. }
  1202. }
  1203. else if (CMND_LEDTABLE == command_code) {
  1204. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
  1205. switch (XdrvMailbox.payload) {
  1206. case 0: // Off
  1207. case 1: // On
  1208. Settings.light_correction = XdrvMailbox.payload;
  1209. break;
  1210. case 2: // Toggle
  1211. Settings.light_correction ^= 1;
  1212. break;
  1213. }
  1214. light_update = 1;
  1215. }
  1216. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.light_correction));
  1217. }
  1218. else if (CMND_RGBWWTABLE == command_code) {
  1219. bool validtable = (XdrvMailbox.data_len > 0);
  1220. char scolor[25];
  1221. if (validtable) {
  1222. uint16_t HSB[3];
  1223. if (strstr(XdrvMailbox.data, ",")) { // Command with up to 5 comma separated parameters
  1224. for (int i = 0; i < LST_RGBWC; i++) {
  1225. char *substr;
  1226. if (0 == i) {
  1227. substr = strtok(XdrvMailbox.data, ",");
  1228. } else {
  1229. substr = strtok(NULL, ",");
  1230. }
  1231. if (substr != NULL) {
  1232. Settings.rgbwwTable[i] = atoi(substr);
  1233. }
  1234. }
  1235. }
  1236. light_update = 1;
  1237. }
  1238. scolor[0] = '\0';
  1239. for (byte i = 0; i < LST_RGBWC; i++) {
  1240. snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
  1241. }
  1242. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, XdrvMailbox.index, scolor);
  1243. }
  1244. else if (CMND_FADE == command_code) {
  1245. switch (XdrvMailbox.payload) {
  1246. case 0: // Off
  1247. case 1: // On
  1248. Settings.light_fade = XdrvMailbox.payload;
  1249. break;
  1250. case 2: // Toggle
  1251. Settings.light_fade ^= 1;
  1252. break;
  1253. }
  1254. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.light_fade));
  1255. }
  1256. else if (CMND_SPEED == command_code) { // 1 - fast, 20 - very slow
  1257. if (('+' == option) && (Settings.light_speed > 1)) {
  1258. XdrvMailbox.payload = Settings.light_speed -1;
  1259. }
  1260. else if (('-' == option) && (Settings.light_speed < STATES)) {
  1261. XdrvMailbox.payload = Settings.light_speed +1;
  1262. }
  1263. if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= STATES)) {
  1264. Settings.light_speed = XdrvMailbox.payload;
  1265. }
  1266. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_speed);
  1267. }
  1268. else if (CMND_WAKEUPDURATION == command_code) {
  1269. if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) {
  1270. Settings.light_wakeup = XdrvMailbox.payload;
  1271. light_wakeup_active = 0;
  1272. }
  1273. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.light_wakeup);
  1274. }
  1275. else if (CMND_UNDOCA == command_code) { // Theos legacy status
  1276. LightGetColor(1, scolor);
  1277. scolor[6] = '\0'; // RGB only
  1278. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,%d,%d,%d,%d,%d"),
  1279. scolor, Settings.light_fade, Settings.light_correction, Settings.light_scheme, Settings.light_speed, Settings.light_width);
  1280. MqttPublishPrefixTopic_P(STAT, XdrvMailbox.topic);
  1281. mqtt_data[0] = '\0';
  1282. }
  1283. else {
  1284. serviced = false; // Unknown command
  1285. }
  1286. if (coldim) {
  1287. LightPreparePower();
  1288. }
  1289. return serviced;
  1290. }
  1291. /*********************************************************************************************\
  1292. * Interface
  1293. \*********************************************************************************************/
  1294. boolean Xdrv04(byte function)
  1295. {
  1296. boolean result = false;
  1297. if (light_type) {
  1298. switch (function) {
  1299. case FUNC_PRE_INIT:
  1300. LightInit();
  1301. break;
  1302. case FUNC_EVERY_50_MSECOND:
  1303. LightAnimate();
  1304. #ifdef USE_ARILUX_RF
  1305. if (pin[GPIO_ARIRFRCV] < 99) AriluxRfHandler();
  1306. #endif // USE_ARILUX_RF
  1307. break;
  1308. #ifdef USE_ARILUX_RF
  1309. case FUNC_EVERY_SECOND:
  1310. if (10 == uptime) AriluxRfInit(); // Needs rest before enabling RF interrupts
  1311. break;
  1312. #endif // USE_ARILUX_RF
  1313. case FUNC_COMMAND:
  1314. result = LightCommand();
  1315. break;
  1316. case FUNC_SET_POWER:
  1317. LightSetPower();
  1318. break;
  1319. }
  1320. }
  1321. return result;
  1322. }