diff --git a/BUILDS.md b/BUILDS.md index 97bef3901c83..c1aa5e2e3c9b 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -29,7 +29,7 @@ | USE_HOTPLUG | - | - | - | - | - | - | - | | | | | | | | | | | Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks -| ROTARY_V1 | - | - | - | - | - | - | - | +| ROTARY_V1 | - | - | x | - | x | - | - | | USE_SONOFF_RF | - | - | x | x | x | - | - | | USE_RF_FLASH | - | - | x | x | x | - | - | | USE_SONOFF_SC | - | - | x | x | x | - | - | diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 45a026fe5f96..a9413ce6bf3e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -66,6 +66,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add command ``Rule0`` to change global rule parameters - Add command ``Time 4`` to display timestamp using milliseconds (#8537) - Add command ``SetOption94 0/1`` to select MAX31855 or MAX6675 thermocouple support (#8616) +- Add command ``SetOption97 0/1`` to switch between Tuya serial speeds 9600 bps (0) or 115200 bps (1) +- Add command ``SetOption98 0/1`` to provide rotary rule triggers (1) instead of controlling light (0) - Add command ``Module2`` to configure fallback module on fast reboot (#8464) - Add commands ``LedPwmOn 0..255``, ``LedPwmOff 0..255`` and ``LedPwmMode1 0/1`` to control led brightness by George (#8491) - Add ESP32 ethernet commands ``EthType 0/1``, ``EthAddress 0..31`` and ``EthClockMode 0..3`` diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index f40ebd895c6b..2535eafeb6bc 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,8 @@ ### 8.3.1.6 20200617 - Add command ``Module2`` to configure fallback module on fast reboot (#8464) +- Add command ``SetOption97 0/1`` to switch between Tuya serial speeds 9600 bps (0) or 115200 bps (1) +- Add command ``SetOption98 0/1`` to provide rotary rule triggers (1) instead of controlling light (0) - Add support for Energy sensor (Denky) for French Smart Metering meter provided by global Energy Providers, need a adaptater. See dedicated full [blog](http://hallard.me/category/tinfo/) about French teleinformation stuff - Add library to be used for decoding Teleinfo (French Metering Smart Meter) - Add support for single wire LMT01 temperature Sensor by justifiably (#8713) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index fa1d90afd459..c073447fe65a 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -430,6 +430,7 @@ // -- Optional modules ---------------------------- #define ROTARY_V1 // Add support for Rotary Encoder as used in MI Desk Lamp (+0k8 code) + #define ROTARY_MAX_STEPS 10 // Rotary step boundary #define USE_SONOFF_RF // Add support for Sonoff Rf Bridge (+3k2 code) #define USE_RF_FLASH // Add support for flashing the EFM8BB1 chip on the Sonoff RF Bridge. C2CK must be connected to GPIO4, C2D to GPIO5 on the PCB (+2k7 code) #define USE_SONOFF_SC // Add support for Sonoff Sc (+1k1 code) diff --git a/tasmota/settings.h b/tasmota/settings.h index 68e4a9db3083..7fd279ab6874 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -115,9 +115,9 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t compress_rules_cpu : 1; // bit 11 (v8.2.0.6) - SetOption93 - Keep uncompressed rules in memory to avoid CPU load of uncompressing at each tick uint32_t max6675 : 1; // bit 12 (v8.3.1.2) - SetOption94 - Implement simpler MAX6675 protocol instead of MAX31855 uint32_t network_wifi : 1; // bit 13 (v8.3.1.3) - CMND_WIFI - uint32_t network_ethernet : 1; // bit 14 (v8.3.1.3) = CMND_ETHERNET + uint32_t network_ethernet : 1; // bit 14 (v8.3.1.3) - CMND_ETHERNET uint32_t tuyamcu_baudrate : 1; // bit 15 (v8.3.1.6) - SetOption97 - Set Baud rate for TuyaMCU serial communication (0 = 9600 or 1 = 115200) - uint32_t spare16 : 1; + uint32_t rotary_uses_rules : 1; // bit 16 (v8.3.1.6) - SetOption98 - Use rules instead of light control uint32_t spare17 : 1; uint32_t spare18 : 1; uint32_t spare19 : 1; diff --git a/tasmota/support_button.ino b/tasmota/support_button.ino index bf058db4e759..2d7c6a415100 100644 --- a/tasmota/support_button.ino +++ b/tasmota/support_button.ino @@ -302,7 +302,7 @@ void ButtonHandler(void) } } } -#if defined(USE_LIGHT) && defined(ROTARY_V1) +#ifdef ROTARY_V1 if (!((0 == button_index) && RotaryButtonPressed())) { #endif if (!Settings.flag3.mqtt_buttons && single_press && SendKey(KEY_BUTTON, button_index + Button.press_counter[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set @@ -344,7 +344,7 @@ void ButtonHandler(void) } } } -#if defined(USE_LIGHT) && defined(ROTARY_V1) +#ifdef ROTARY_V1 } #endif Button.press_counter[button_index] = 0; diff --git a/tasmota/support_rotary.ino b/tasmota/support_rotary.ino index 1eb6bbcdbb0d..cd80b44840cf 100644 --- a/tasmota/support_rotary.ino +++ b/tasmota/support_rotary.ino @@ -17,35 +17,72 @@ along with this program. If not, see . */ -#ifdef USE_LIGHT #ifdef ROTARY_V1 /*********************************************************************************************\ * Rotary support + * + * Supports full range in 10 steps of the Rotary Encoder: + * - Light Dimmer + * - Light Color for RGB lights when Button1 pressed + * - Light Color Temperature for CW lights when Button1 pressed + * + * _______ _______ + * GPIO_ROT1A ______| |_______| |______ GPIO_ROT1A + * negative <-- _______ _______ __ --> positive + * GPIO_ROT1B __| |_______| |_______| GPIO_ROT1B + * \*********************************************************************************************/ -#define ROTARY_OPTION1 -//#define ROTARY_OPTION2 +#ifndef ROTARY_MAX_STEPS +#define ROTARY_MAX_STEPS 10 // Rotary step boundary +#endif + +//#define ROTARY_OPTION1 // Up to 4 interrupts and pulses per step +//#define ROTARY_OPTION2 // Up to 4 interrupts but 1 pulse per step +#define ROTARY_OPTION3 // 1 interrupt and pulse per step #ifdef ROTARY_OPTION1 -const int8_t rotary_dimmer_increment = 1; -const int8_t rotary_ct_increment = 2; -const int8_t rotary_color_increment = 4; -#endif +// up to 4 pulses per step +const uint8_t rotary_dimmer_increment = 100 / (ROTARY_MAX_STEPS * 3); // Dimmer 1..100 = 100 +const uint8_t rotary_ct_increment = 350 / (ROTARY_MAX_STEPS * 3); // Ct 153..500 = 347 +const uint8_t rotary_color_increment = 360 / (ROTARY_MAX_STEPS * 3); // Hue 0..359 = 360 +#endif // ROTARY_OPTION1 #ifdef ROTARY_OPTION2 -const int8_t rotary_dimmer_increment = 2; -const int8_t rotary_ct_increment = 8; -const int8_t rotary_color_increment = 8; -#endif +// 1 pulse per step +const uint8_t rotary_dimmer_increment = 100 / ROTARY_MAX_STEPS; // Dimmer 1..100 = 100 +const uint8_t rotary_ct_increment = 350 / ROTARY_MAX_STEPS; // Ct 153..500 = 347 +const uint8_t rotary_color_increment = 360 / ROTARY_MAX_STEPS; // Hue 0..359 = 360 +#endif // ROTARY_OPTION2 + +#ifdef ROTARY_OPTION3 +// 1 pulse per step +const uint8_t rotary_dimmer_increment = 100 / ROTARY_MAX_STEPS; // Dimmer 1..100 = 100 +const uint8_t rotary_ct_increment = 350 / ROTARY_MAX_STEPS; // Ct 153..500 = 347 +const uint8_t rotary_color_increment = 360 / ROTARY_MAX_STEPS; // Hue 0..359 = 360 +#endif // ROTARY_OPTION3 + +const uint8_t ROTARY_TIMEOUT = 10; // 10 * RotaryHandler() call which is usually 10 * 0.05 seconds struct ROTARY { - uint8_t present = 0; +#ifdef ROTARY_OPTION1 uint8_t state = 0; - uint8_t prevNextCode; +#endif // ROTARY_OPTION1 +#ifdef ROTARY_OPTION2 uint16_t store; - int8_t position = 128; - int8_t last_position = 128; - uint8_t changed = 0; + uint8_t prev_next_code; +#endif // ROTARY_OPTION2 +#ifdef ROTARY_OPTION3 + uint32_t debounce = 0; +#endif // ROTARY_OPTION3 + int8_t abs_position1 = 0; + int8_t abs_position2 = 0; + int8_t direction = 0; // Control consistent direction + uint8_t present = 0; + uint8_t position = 128; + uint8_t last_position = 128; + uint8_t timeout = 0; // Disallow direction change within 0.5 second + bool changed = false; bool busy = false; } Rotary; @@ -53,10 +90,18 @@ struct ROTARY { void update_rotary(void) ICACHE_RAM_ATTR; void update_rotary(void) { - if (Rotary.busy || !LightPowerIRAM()) { return; } + if (Rotary.busy) { return; } + bool powered_on = (power); +#ifdef USE_LIGHT + if (!Settings.flag4.rotary_uses_rules) { // SetOption98 - Use rules instead of light control + powered_on = (LightPowerIRAM()); + } +#endif // USE_LIGHT + if (!powered_on) { return; } #ifdef ROTARY_OPTION1 // https://github.com/PaulStoffregen/Encoder/blob/master/Encoder.h +/* uint8_t p1val = digitalRead(Pin(GPIO_ROT1A)); uint8_t p2val = digitalRead(Pin(GPIO_ROT1B)); uint8_t state = Rotary.state & 3; @@ -77,35 +122,105 @@ void update_rotary(void) { Rotary.position -= 2; return; } -#endif +*/ + uint8_t p1val = digitalRead(Pin(GPIO_ROT1A)); + uint8_t p2val = digitalRead(Pin(GPIO_ROT1B)); + uint8_t state = Rotary.state & 3; + if (p1val) { state |= 4; } + if (p2val) { state |= 8; } + Rotary.state = (state >> 2); + int direction = 0; + int multiply = 1; + switch (state) { + case 3: case 12: + multiply = 2; + case 1: case 7: case 8: case 14: + direction = 1; + break; + case 6: case 9: + multiply = 2; + case 2: case 4: case 11: case 13: + direction = -1; + break; + } + if ((0 == Rotary.direction) || (direction == Rotary.direction)) { + Rotary.position += (direction * multiply); + Rotary.direction = direction; + } +#endif // ROTARY_OPTION1 #ifdef ROTARY_OPTION2 // https://github.com/FrankBoesing/EncoderBounce/blob/master/EncoderBounce.h +/* const uint16_t rot_enc = 0b0110100110010110; uint8_t p1val = digitalRead(Pin(GPIO_ROT1B)); uint8_t p2val = digitalRead(Pin(GPIO_ROT1A)); - uint8_t t = Rotary.prevNextCode; + uint8_t t = Rotary.prev_next_code; t <<= 2; if (p1val) { t |= 0x02; } if (p2val) { t |= 0x01; } t &= 0x0f; - Rotary.prevNextCode = t; + Rotary.prev_next_code = t; // If valid then store as 16 bit data. if (rot_enc & (1 << t)) { - Rotary.store = (Rotary.store << 4) | Rotary.prevNextCode; + Rotary.store = (Rotary.store << 4) | Rotary.prev_next_code; if (Rotary.store == 0xd42b) { Rotary.position++; } else if (Rotary.store == 0xe817) { Rotary.position--; } else if ((Rotary.store & 0xff) == 0x2b) { Rotary.position--; } else if ((Rotary.store & 0xff) == 0x17) { Rotary.position++; } } -#endif +*/ + const uint16_t rot_enc = 0b0110100110010110; + + uint8_t p1val = digitalRead(Pin(GPIO_ROT1B)); + uint8_t p2val = digitalRead(Pin(GPIO_ROT1A)); + uint8_t t = Rotary.prev_next_code; + t <<= 2; + if (p1val) { t |= 0x02; } + if (p2val) { t |= 0x01; } + t &= 0x0f; + Rotary.prev_next_code = t; + + // If valid then store as 16 bit data. + if (rot_enc & (1 << t)) { + Rotary.store = (Rotary.store << 4) | Rotary.prev_next_code; + int direction = 0; + if (Rotary.store == 0xd42b) { direction = 1; } + else if (Rotary.store == 0xe817) { direction = -1; } + else if ((Rotary.store & 0xff) == 0x2b) { direction = -1; } + else if ((Rotary.store & 0xff) == 0x17) { direction = 1; } + if ((0 == Rotary.direction) || (direction == Rotary.direction)) { + Rotary.position += direction; + Rotary.direction = direction; + } + } +#endif // ROTARY_OPTION2 + +#ifdef ROTARY_OPTION3 + // Theo Arends + uint32_t time = micros(); + if (Rotary.debounce < time) { + int direction = (digitalRead(Pin(GPIO_ROT1B))) ? 1 : -1; + if ((0 == Rotary.direction) || (direction == Rotary.direction)) { + Rotary.position += direction; + Rotary.direction = direction; + } + Rotary.debounce = time +20; // Experimental debounce + } +#endif // ROTARY_OPTION3 } bool RotaryButtonPressed(void) { - if (Rotary.changed && LightPower()) { - Rotary.changed = 0; // Color temp changed, no need to turn of the light + bool powered_on = (power); +#ifdef USE_LIGHT + if (!Settings.flag4.rotary_uses_rules) { // SetOption98 - Use rules instead of light control + powered_on = LightPower(); + } +#endif // USE_LIGHT + if (Rotary.changed && powered_on) { + Rotary.changed = false; // Color (temp) changed, no need to turn of the light return true; } return false; @@ -116,9 +231,13 @@ void RotaryInit(void) { if (PinUsed(GPIO_ROT1A) && PinUsed(GPIO_ROT1B)) { Rotary.present++; pinMode(Pin(GPIO_ROT1A), INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(Pin(GPIO_ROT1A)), update_rotary, CHANGE); pinMode(Pin(GPIO_ROT1B), INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(Pin(GPIO_ROT1B)), update_rotary, CHANGE); +#ifdef ROTARY_OPTION3 + attachInterrupt(Pin(GPIO_ROT1A), update_rotary, RISING); +#else + attachInterrupt(Pin(GPIO_ROT1A), update_rotary, CHANGE); + attachInterrupt(Pin(GPIO_ROT1B), update_rotary, CHANGE); +#endif } } @@ -127,31 +246,56 @@ void RotaryInit(void) { \*********************************************************************************************/ void RotaryHandler(void) { + if (Rotary.timeout) { + Rotary.timeout--; + if (!Rotary.timeout) { + Rotary.direction = 0; + } + } if (Rotary.last_position == Rotary.position) { return; } - Rotary.busy = true; - int8_t rotary_position = Rotary.position - Rotary.last_position; - Rotary.last_position = 128; - Rotary.position = 128; + Rotary.timeout = ROTARY_TIMEOUT; // Prevent fast direction changes within 0.5 second + + int rotary_position = Rotary.position - Rotary.last_position; if (Settings.save_data && (save_data_counter < 2)) { - save_data_counter = 2; // Postpone flash writes while rotary is turned + save_data_counter = 2; // Postpone flash writes while rotary is turned } - if (Button.hold_timer[0]) { // Button1 is pressed: set color temperature - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ROT: CT/Color position %d"), rotary_position); - Rotary.changed = 1; - if (!LightColorTempOffset(rotary_position * rotary_ct_increment)) { // Ct 153..500 = (500 - 153) / 8 = 43 steps - LightColorOffset(rotary_position * rotary_color_increment); // Hue 0..359 = 360 / 8 = 45 steps + bool button_pressed = (Button.hold_timer[0]); // Button1 is pressed: set color temperature + if (button_pressed) { Rotary.changed = true; } +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ROT: Button1 %d, Position %d"), button_pressed, rotary_position); + +#ifdef USE_LIGHT + if (!Settings.flag4.rotary_uses_rules) { // SetOption98 - Use rules instead of light control + if (button_pressed) { + if (!LightColorTempOffset(rotary_position * rotary_ct_increment)) { + LightColorOffset(rotary_position * rotary_color_increment); + } + } else { + LightDimmerOffset(rotary_position * rotary_dimmer_increment); } } else { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ROT: Dimmer position %d"), rotary_position); - LightDimmerOffset(rotary_position * rotary_dimmer_increment); // Dimmer 1..100 = 100 / 2 = 50 steps +#endif // USE_LIGHT + if (button_pressed) { + Rotary.abs_position2 += rotary_position; + if (Rotary.abs_position2 < 0) { Rotary.abs_position2 = 0; } + if (Rotary.abs_position2 > ROTARY_MAX_STEPS) { Rotary.abs_position2 = ROTARY_MAX_STEPS; } + } else { + Rotary.abs_position1 += rotary_position; + if (Rotary.abs_position1 < 0) { Rotary.abs_position1 = 0; } + if (Rotary.abs_position1 > ROTARY_MAX_STEPS) { Rotary.abs_position1 = ROTARY_MAX_STEPS; } + } + Response_P(PSTR("{\"Rotary1\":{\"Pos1\":%d,\"Pos2\":%d}}"), Rotary.abs_position1, Rotary.abs_position2); + XdrvRulesProcess(); +#ifdef USE_LIGHT } +#endif // USE_LIGHT + Rotary.last_position = 128; + Rotary.position = 128; Rotary.busy = false; } #endif // ROTARY_V1 -#endif // USE_LIGHT diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index b89c63a481e0..c677d4e57ab7 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -372,11 +372,9 @@ void loop(void) { if (TimeReached(state_50msecond)) { SetNextTimeInterval(state_50msecond, 50); -#ifdef USE_LIGHT #ifdef ROTARY_V1 RotaryHandler(); #endif // ROTARY_V1 -#endif // USE_LIGHT XdrvCall(FUNC_EVERY_50_MSECOND); XsnsCall(FUNC_EVERY_50_MSECOND); }