diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 42b97ddcb..1d133a47d 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -11,7 +11,9 @@ * Add support for Luminea ZX2820 Smart Socket with Energy monitoring (#4921) * Add define MDNS_ENABLE to control initial mDNS state (#4923) * Add split interlock part 1 (#4910) - * + * Add support for rotary switch + * Add support for Mi Desk Lamp + * * 6.4.1.7 20190106 * Fix HLW8012, HJL01 and BL0937 based energy sensors low Power (below 10W) measurement regression from 6.4.1.6 * Add Power status functionality to LED2 when configured leaving LED1 for Link status indication diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 59b7a96f2..c520d3981 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -260,5 +260,7 @@ const uint8_t kIFan02Speed[MAX_FAN_SPEED][3] = {{6,6,6}, {7,6,6}, {7,7,6}, {7,6, \*********************************************************************************************/ extern uint8_t light_device; // Light device number +extern uint8_t light_power; // Light power +extern uint8_t rotary_changed; // Rotary switch changed #endif // _SONOFF_H_ diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 16239e046..d4ccbc904 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -2362,6 +2362,7 @@ void GpioInit(void) ButtonInit(); SwitchInit(); + RotaryInit(); #ifdef USE_WS2812 if (!light_type && (pin[GPIO_WS2812] < 99)) { // RGB led @@ -2548,6 +2549,7 @@ void loop(void) ButtonLoop(); SwitchLoop(); + RotaryLoop(); if (TimeReached(state_50msecond)) { SetNextTimeInterval(state_50msecond, 50); diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 66a83cd1e..5de0caf74 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -163,6 +163,8 @@ enum ProgramSelectablePins { GPIO_DI, // my92x1 PWM input GPIO_DCKI, // my92x1 CLK input GPIO_ARIRFRCV, // AliLux RF Receive input + GPIO_ROT_A, // Rotary switch A Pin + GPIO_ROT_B, // Rotary switch B Pin GPIO_USER, // User configurable GPIO_MAX }; @@ -278,6 +280,7 @@ enum SupportedModules { DIGOO, KA10, ZX2820, + MI_DESK_LAMP, MAXMODULE }; /********************************************************************************************/ @@ -565,6 +568,7 @@ const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = { ARILUX_LC11, ZENGGE_ZF_WF017, HUAFAN_SS, + MI_DESK_LAMP, KMC_70011, AILIGHT, // Light Bulbs PHILIPS, @@ -1754,6 +1758,23 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_LED1_INV, // GPIO13 Green Led - Link and Power status GPIO_REL1, // GPIO14 Relay 0, 0, 0 + }, + { "Mi Desk Lamp", // Mi LED Desk Lamp + // https://www.mi.com/global/smartlamp/ + 0, 0, + GPIO_KEY1, // GPIO02 Button + 0, + GPIO_PWM1, // GPIO04 Cold White + GPIO_PWM2, // GPIO05 Warm White + // GPIO06 (SD_CLK Flash) + // GPIO07 (SD_DATA0 Flash QIO/DIO/DOUT) + // GPIO08 (SD_DATA1 Flash QIO/DIO/DOUT) + 0, + 0, + // GPIO11 (SD_CMD Flash) + GPIO_ROT_A, // GPIO12 Rotary switch A pin + GPIO_ROT_B, // GPIO13 Rotary switch B pin + 0, 0, 0, 0 } }; diff --git a/sonoff/support_button.ino b/sonoff/support_button.ino index ed5cecce6..dd4b298c8 100644 --- a/sonoff/support_button.ino +++ b/sonoff/support_button.ino @@ -203,19 +203,23 @@ void ButtonHandler(void) multipress[button_index] = 1; } } + if ((MI_DESK_LAMP == Settings.module) && (button_index == 0) && (rotary_changed) && (light_power)) { + rotary_changed = 0; // Color temp changed, no need to turn of the light + } else { if (single_press && SendKey(0, button_index + multipress[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set // Success } else { - if (multipress[button_index] < 3) { // Single or Double press - if (WifiState() > WIFI_RESTART) { // WPSconfig, Smartconfig or Wifimanager active - restart_flag = 1; - } else { - ExecuteCommandPower(button_index + multipress[button_index], POWER_TOGGLE, SRC_BUTTON); // Execute Toggle command internally - } - } else { // 3 - 7 press - if (!Settings.flag.button_restrict) { - snprintf_P(scmnd, sizeof(scmnd), kCommands[multipress[button_index] -3]); - ExecuteCommand(scmnd, SRC_BUTTON); + if (multipress[button_index] < 3) { // Single or Double press + if (WifiState() > WIFI_RESTART) { // WPSconfig, Smartconfig or Wifimanager active + restart_flag = 1; + } else { + ExecuteCommandPower(button_index + multipress[button_index], POWER_TOGGLE, SRC_BUTTON); // Execute Toggle command internally + } + } else { // 3 - 7 press + if (!Settings.flag.button_restrict) { + snprintf_P(scmnd, sizeof(scmnd), kCommands[multipress[button_index] -3]); + ExecuteCommand(scmnd, SRC_BUTTON); + } } } } diff --git a/sonoff/support_rotary.ino b/sonoff/support_rotary.ino new file mode 100644 index 000000000..d11450858 --- /dev/null +++ b/sonoff/support_rotary.ino @@ -0,0 +1,151 @@ +/* + support_rotary.ino - rotary switch support for Sonoff-Tasmota + + Copyright (C) 2019 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#define ROTARY_V1 +#ifdef ROTARY_V1 +/*********************************************************************************************\ + * Rotary support +\*********************************************************************************************/ + +unsigned long rotary_debounce = 0; // Rotary debounce timer +uint8_t rotaries_found = 0; +uint8_t rotary_state = 0; +uint8_t rotary_position = 128; +uint8_t rotary_last_position = 128; +uint8_t interrupts_in_use = 0; +uint8_t rotary_changed = 0; + +/********************************************************************************************/ + +void update_position(void) { + uint8_t s; + + /* + * https://github.com/PaulStoffregen/Encoder/blob/master/Encoder.h + */ + + s = rotary_state & 3; + if (digitalRead(pin[GPIO_ROT_A])) s |= 4; + if (digitalRead(pin[GPIO_ROT_B])) s |= 8; + switch (s) { + case 0: case 5: case 10: case 15: + break; + case 1: case 7: case 8: case 14: + rotary_position++; break; + case 2: case 4: case 11: case 13: + rotary_position--; break; + case 3: case 12: + rotary_position = rotary_position + 2; break; + default: + rotary_position = rotary_position - 2; break; + } + rotary_state = (s >> 2); +} + +void update_rotary(void) { + if (MI_DESK_LAMP == Settings.module){ + if (light_power) { + update_position(); + } + } +} + +void RotaryInit(void) +{ + rotaries_found = 0; + if ((pin[GPIO_ROT_A] < 99) && (pin[GPIO_ROT_B] < 99)) { + rotaries_found++; + pinMode(pin[GPIO_ROT_A], INPUT_PULLUP); + pinMode(pin[GPIO_ROT_B], INPUT_PULLUP); + + // GPIO6-GPIO11 are typically used to interface with the flash memory IC on + // most esp8266 modules, so we should avoid adding interrupts to these pins. + + if ((pin[GPIO_ROT_A] < 6) || (pin[GPIO_ROT_A] > 11)) { + attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT_A]), update_rotary, CHANGE); + interrupts_in_use++; + } + if ((pin[GPIO_ROT_B] < 6) || (pin[GPIO_ROT_B] > 11)) { + attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT_B]), update_rotary, CHANGE); + interrupts_in_use++; + } + } +} + +/*********************************************************************************************\ + * Rotary handler +\*********************************************************************************************/ + +void RotaryHandler(void) +{ + if (interrupts_in_use < 2) { + noInterrupts(); + update_rotary(); + } else { + noInterrupts(); + } + if (rotary_last_position != rotary_position) { + if (MI_DESK_LAMP == Settings.module) { // Mi Desk lamp + if (holdbutton[0]) { + rotary_changed = 1; + // button1 is pressed: set color temperature + int16_t t = LightGetColorTemp(); + t = t + (rotary_position - rotary_last_position); + if (t < 153) { + t = 153; + } + if (t > 500) { + t = 500; + } + snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_CMND_COLORTEMPERATURE " %d"), rotary_position - rotary_last_position); + AddLog(LOG_LEVEL_DEBUG); + LightSetColorTemp((uint16_t)t); + } else { + int8_t d = Settings.light_dimmer; + d = d + (rotary_position - rotary_last_position); + if (d < 1) { + d = 1; + } + if (d > 100) { + d = 100; + } + snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_CMND_DIMMER " %d"), rotary_position - rotary_last_position); + AddLog(LOG_LEVEL_DEBUG); + + LightSetDimmer((uint8_t)d); + Settings.light_dimmer = d; + } + } + rotary_last_position = 128; + rotary_position = 128; + } + interrupts(); +} + +void RotaryLoop(void) +{ + if (rotaries_found) { + if (TimeReached(rotary_debounce)) { + SetNextTimeInterval(rotary_debounce, Settings.button_debounce); // Using button_debounce setting for this as well + RotaryHandler(); + } + } +} + +#endif // ROTARY_V1