diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 35be41b13e97..981557afa934 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -1,4 +1,9 @@ -/* 6.4.1.1 20181224 +/* 6.4.1.2 20181228 + * Change switch driver making it modular and introduce input filter (#4665, #4724) + * Add define DS18B20_INTERNAL_PULLUP to select internal input pullup when only one DS18B20 sensor is connected eliminating external resistor (#4738) + * Add variable %timestamp% to rules (#4749) + * + * 6.4.1.1 20181224 * Fix most compiler warnings * Change switch input detection by optimizing switch debounce (#4724) * diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 561709f519ed..da6efe8cbf93 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -109,7 +109,6 @@ unsigned long pulse_timer[MAX_PULSETIMERS] = { 0 }; // Power off timer unsigned long blink_timer = 0; // Power cycle timer unsigned long backlog_delay = 0; // Command backlog delay unsigned long button_debounce = 0; // Button debounce timer -unsigned long switch_debounce = 0; // Switch debounce timer power_t power = 0; // Current copy of Settings.power power_t blink_power; // Blink power state power_t blink_mask = 0; // Blink relay active mask @@ -127,7 +126,6 @@ int blinks = 201; // Number of LED blinks uint32_t uptime = 0; // Counting every second until 4294967295 = 130 year uint32_t loop_load_avg = 0; // Indicative loop load average uint32_t global_update = 0; // Timestamp of last global temperature and humidity update -uint32_t switch_change[MAX_SWITCHES]; // Timestamp of last switch change float global_temperature = 0; // Provide a global temperature to be used by some sensors float global_humidity = 0; // Provide a global humidity to be used by some sensors char *ota_url; // OTA url string pointer @@ -137,7 +135,6 @@ uint16_t blink_counter = 0; // Number of blink cycles uint16_t seriallog_timer = 0; // Timer to disable Seriallog uint16_t syslog_timer = 0; // Timer to re-enable syslog_level uint16_t holdbutton[MAX_KEYS] = { 0 }; // Timer for button hold -uint16_t switch_no_pullup = 0; // Switch pull-up bitmask flags int16_t save_data_counter; // Counter and flag for config save to Flash RulesBitfield rules_flag; // Rule state flags (16 bits) uint8_t serial_local = 0; // Handle serial locally; @@ -155,9 +152,6 @@ uint8_t blinkspeed = 1; // LED blink rate uint8_t lastbutton[MAX_KEYS] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED }; // Last button states uint8_t multiwindow[MAX_KEYS] = { 0 }; // Max time between button presses to record press count uint8_t multipress[MAX_KEYS] = { 0 }; // Number of button presses within multiwindow -uint8_t lastwallswitch[MAX_SWITCHES]; // Last wall switch states -uint8_t holdwallswitch[MAX_SWITCHES] = { 0 }; // Timer for wallswitch push button hold -uint8_t virtualswitch[MAX_SWITCHES]; // Virtual switch states uint8_t pin[GPIO_MAX]; // Possible pin configurations uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) @@ -1163,7 +1157,6 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len) else if ((CMND_SWITCHMODE == command_code) && (index > 0) && (index <= MAX_SWITCHES)) { if ((payload >= 0) && (payload < MAX_SWITCH_OPTION)) { Settings.switchmode[index -1] = payload; - GpioSwitchPinMode(index -1); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, Settings.switchmode[index-1]); } @@ -1648,7 +1641,7 @@ boolean MqttShowSensor(void) if (pin[GPIO_SWT1 +i] < 99) { #endif // USE_TM1638 boolean swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i])); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_SWITCH "%d\":\"%s\""), mqtt_data, i +1, GetStateText(swm ^ lastwallswitch[i])); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_SWITCH "%d\":\"%s\""), mqtt_data, i +1, GetStateText(swm ^ SwitchLastState(i))); } } XsnsCall(FUNC_JSON_APPEND); @@ -1888,93 +1881,6 @@ void ButtonHandler(void) } } -/*********************************************************************************************\ - * Switch handler -\*********************************************************************************************/ - -void SwitchHandler(byte mode) -{ - uint8_t button = NOT_PRESSED; - uint8_t switchflag; - uint16_t loops_per_second = 1000 / Settings.switch_debounce; - - for (byte i = 0; i < MAX_SWITCHES; i++) { - if (((pin[GPIO_SWT1 +i] < 99) && (TimePassedSince(switch_change[i]) > Settings.switch_debounce)) || (mode)) { - - if (holdwallswitch[i]) { - holdwallswitch[i]--; - if (0 == holdwallswitch[i]) { - SendKey(1, i +1, 3); // Execute command via MQTT - } - } - - if (mode) { - button = virtualswitch[i]; - } else { - if (!((uptime < 4) && (0 == pin[GPIO_SWT1 +i]))) { // Block GPIO0 for 4 seconds after poweron to workaround Wemos D1 RTS circuit - button = digitalRead(pin[GPIO_SWT1 +i]); - } - } - - if (button != lastwallswitch[i]) { - switchflag = 3; - switch (Settings.switchmode[i]) { - case TOGGLE: - switchflag = 2; // Toggle - break; - case FOLLOW: - switchflag = button &1; // Follow wall switch state - break; - case FOLLOW_INV: - switchflag = ~button &1; // Follow inverted wall switch state - break; - case PUSHBUTTON: - if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { - switchflag = 2; // Toggle with pushbutton to Gnd - } - break; - case PUSHBUTTON_INV: - if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { - switchflag = 2; // Toggle with releasing pushbutton from Gnd - } - break; - case PUSHBUTTON_TOGGLE: - if (button != lastwallswitch[i]) { - switchflag = 2; // Toggle with any pushbutton change - } - break; - case PUSHBUTTONHOLD: - if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { - holdwallswitch[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; - } - if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { - holdwallswitch[i] = 0; - switchflag = 2; // Toggle with pushbutton to Gnd - } - break; - case PUSHBUTTONHOLD_INV: - if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { - holdwallswitch[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; - } - if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { - holdwallswitch[i] = 0; - switchflag = 2; // Toggle with pushbutton to Gnd - } - break; - } - - if (switchflag < 3) { - if (!SendKey(1, i +1, switchflag)) { // Execute command via MQTT - ExecuteCommandPower(i +1, switchflag, SRC_SWITCH); // Execute command internally (if i < devices_present) - } - } - - lastwallswitch[i] = button; - } - } - } -} - /*********************************************************************************************\ * State loops \*********************************************************************************************/ @@ -2404,64 +2310,8 @@ void SerialInput(void) serial_in_byte_counter = 0; } } -/********************************************************************************************/ -void SwitchChange(byte index) -{ - switch_change[index] = millis(); -} - -void SwitchChange1(void) -{ - SwitchChange(0); -} - -void SwitchChange2(void) -{ - SwitchChange(1); -} - -void SwitchChange3(void) -{ - SwitchChange(2); -} - -void SwitchChange4(void) -{ - SwitchChange(3); -} - -void SwitchChange5(void) -{ - SwitchChange(4); -} - -void SwitchChange6(void) -{ - SwitchChange(5); -} - -void SwitchChange7(void) -{ - SwitchChange(6); -} - -void SwitchChange8(void) -{ - SwitchChange(7); -} - -void GpioSwitchPinMode(uint8_t index) -{ - if ((pin[GPIO_SWT1 +index] < 99) && (index < MAX_SWITCHES)) { - pinMode(pin[GPIO_SWT1 +index], (16 == pin[GPIO_SWT1 +index]) ? INPUT_PULLDOWN_16 : bitRead(switch_no_pullup, index) ? INPUT : INPUT_PULLUP); - - typedef void (*function)(void) ; - function switch_callbacks[MAX_SWITCHES] = { SwitchChange1, SwitchChange2, SwitchChange3, SwitchChange4, SwitchChange5, SwitchChange6, SwitchChange7, SwitchChange8 }; - detachInterrupt(pin[GPIO_SWT1 +index]); - attachInterrupt(pin[GPIO_SWT1 +index], switch_callbacks[index], CHANGE); - } -} +/********************************************************************************************/ void GpioInit(void) { @@ -2499,7 +2349,7 @@ void GpioInit(void) if (mpin) { if ((mpin >= GPIO_SWT1_NP) && (mpin < (GPIO_SWT1_NP + MAX_SWITCHES))) { - bitSet(switch_no_pullup, mpin - GPIO_SWT1_NP); + SwitchPullupFlag(mpin - GPIO_SWT1_NP); mpin -= (GPIO_SWT1_NP - GPIO_SWT1); } else if ((mpin >= GPIO_KEY1_NP) && (mpin < (GPIO_KEY1_NP + MAX_KEYS))) { @@ -2631,14 +2481,8 @@ void GpioInit(void) digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); } } - for (byte i = 0; i < MAX_SWITCHES; i++) { - lastwallswitch[i] = 1; // Init global to virtual switch state; - if (pin[GPIO_SWT1 +i] < 99) { - GpioSwitchPinMode(i); - lastwallswitch[i] = digitalRead(pin[GPIO_SWT1 +i]); // Set global now so doesn't change the saved power state on first switch check - } - virtualswitch[i] = lastwallswitch[i]; - } + + SwitchInit(); #ifdef USE_WS2812 if (!light_type && (pin[GPIO_WS2812] < 99)) { // RGB led @@ -2826,10 +2670,9 @@ void loop(void) SetNextTimeInterval(button_debounce, Settings.button_debounce); ButtonHandler(); } - if (TimeReached(switch_debounce)) { - SetNextTimeInterval(switch_debounce, Settings.switch_debounce); - SwitchHandler(0); - } + + SwitchLoop(); + if (TimeReached(state_50msecond)) { SetNextTimeInterval(state_50msecond, 50); XdrvCall(FUNC_EVERY_50_MSECOND); diff --git a/sonoff/sonoff_version.h b/sonoff/sonoff_version.h index 532547a63417..490491e1cd01 100644 --- a/sonoff/sonoff_version.h +++ b/sonoff/sonoff_version.h @@ -20,7 +20,7 @@ #ifndef _SONOFF_VERSION_H_ #define _SONOFF_VERSION_H_ -#define VERSION 0x06040101 +#define VERSION 0x06040102 #define D_PROGRAMNAME "Sonoff-Tasmota" #define D_AUTHOR "Theo Arends" diff --git a/sonoff/support_switch.ino b/sonoff/support_switch.ino new file mode 100644 index 000000000000..7e53cffb1155 --- /dev/null +++ b/sonoff/support_switch.ino @@ -0,0 +1,202 @@ +/* + support_switch.ino - switch support for Sonoff-Tasmota + + Copyright (C) 2018 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 SWITCH_V2 +#ifdef SWITCH_V2 +/*********************************************************************************************\ + * Switch support with input filter + * + * Inspired by (https://github.com/OLIMEX/olimex-iot-firmware-esp8266/blob/master/olimex/user/user_switch2.c) +\*********************************************************************************************/ + +#define SWITCH_STATE_FILTER 5 + +#include + +Ticker TickerSwitch; + +unsigned long switch_debounce = 0; // Switch debounce timer +uint16_t switch_no_pullup = 0; // Switch pull-up bitmask flags +uint8_t switch_state_buf[MAX_SWITCHES] = { SWITCH_STATE_FILTER / 2 }; +uint8_t lastwallswitch[MAX_SWITCHES]; // Last wall switch states +uint8_t holdwallswitch[MAX_SWITCHES] = { 0 }; // Timer for wallswitch push button hold +uint8_t switch_virtual[MAX_SWITCHES]; // Virtual switch states +uint8_t switches_found = 0; + +/********************************************************************************************/ + +void SwitchPullupFlag(uint16 switch_bit) +{ + bitSet(switch_no_pullup, switch_bit); +} + +uint8_t SwitchLastState(uint8_t index) +{ + return lastwallswitch[index]; +} + +void SwitchSetVirtual(uint8_t index, uint8_t state) +{ + switch_virtual[index] = state; +} + +uint8_t SwitchGetVirtual(uint8_t index) +{ + return switch_virtual[index]; +} + +/*********************************************************************************************/ + +void SwitchProbe(void) +{ + for (byte i = 0; i < MAX_SWITCHES; i++) { + if (pin[GPIO_SWT1 +i] < 99) { + if (!((uptime < 4) && (0 == pin[GPIO_SWT1 +i]))) { // Block GPIO0 for 4 seconds after poweron to workaround Wemos D1 RTS circuit + // Olimex user_switch2.c code to fix 50Hz induced pulses + if (1 == digitalRead(pin[GPIO_SWT1 +i])) { + if (switch_state_buf[i] < SWITCH_STATE_FILTER) { + switch_state_buf[i]++; + if (SWITCH_STATE_FILTER == switch_state_buf[i]) { + switch_virtual[i] = 1; + } + } + } else { + if (switch_state_buf[i] > 0) { + switch_state_buf[i]--; + if (0 == switch_state_buf[i]) { + switch_virtual[i] = 0; + } + } + } + } + } + } + TickerSwitch.attach_ms(10, SwitchProbe); // Re-arm as core 2.3.0 does only support ONCE mode +} + +void SwitchInit(void) +{ + switches_found = 0; + for (byte i = 0; i < MAX_SWITCHES; i++) { + lastwallswitch[i] = 1; // Init global to virtual switch state; + if (pin[GPIO_SWT1 +i] < 99) { + switches_found++; + pinMode(pin[GPIO_SWT1 +i], (16 == pin[GPIO_SWT1 +i]) ? INPUT_PULLDOWN_16 : bitRead(switch_no_pullup, i) ? INPUT : INPUT_PULLUP); + lastwallswitch[i] = digitalRead(pin[GPIO_SWT1 +i]); // Set global now so doesn't change the saved power state on first switch check + } + switch_virtual[i] = lastwallswitch[i]; + } + if (switches_found) { TickerSwitch.attach_ms(10, SwitchProbe); } +} + +/*********************************************************************************************\ + * Switch handler +\*********************************************************************************************/ + +void SwitchHandler(byte mode) +{ + uint8_t button = NOT_PRESSED; + uint8_t switchflag; + uint16_t loops_per_second = 1000 / Settings.switch_debounce; + + for (byte i = 0; i < MAX_SWITCHES; i++) { + if ((pin[GPIO_SWT1 +i] < 99) || (mode)) { + + + if (holdwallswitch[i]) { + holdwallswitch[i]--; + if (0 == holdwallswitch[i]) { + SendKey(1, i +1, 3); // Execute command via MQTT + } + } + + button = switch_virtual[i]; + +// enum SwitchModeOptions {TOGGLE, FOLLOW, FOLLOW_INV, PUSHBUTTON, PUSHBUTTON_INV, PUSHBUTTONHOLD, PUSHBUTTONHOLD_INV, PUSHBUTTON_TOGGLE, MAX_SWITCH_OPTION}; + + if (button != lastwallswitch[i]) { + switchflag = 3; + switch (Settings.switchmode[i]) { + case TOGGLE: + switchflag = 2; // Toggle + break; + case FOLLOW: + switchflag = button &1; // Follow wall switch state + break; + case FOLLOW_INV: + switchflag = ~button &1; // Follow inverted wall switch state + break; + case PUSHBUTTON: + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + case PUSHBUTTON_INV: + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { + switchflag = 2; // Toggle with releasing pushbutton from Gnd + } + break; + case PUSHBUTTON_TOGGLE: + if (button != lastwallswitch[i]) { + switchflag = 2; // Toggle with any pushbutton change + } + break; + case PUSHBUTTONHOLD: + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { + holdwallswitch[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { + holdwallswitch[i] = 0; + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + case PUSHBUTTONHOLD_INV: + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { + holdwallswitch[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; + } + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { + holdwallswitch[i] = 0; + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + } + + if (switchflag < 3) { + if (!SendKey(1, i +1, switchflag)) { // Execute command via MQTT + ExecuteCommandPower(i +1, switchflag, SRC_SWITCH); // Execute command internally (if i < devices_present) + } + } + + lastwallswitch[i] = button; + } + } + } +} + +void SwitchLoop(void) +{ + if (switches_found) { + if (TimeReached(switch_debounce)) { + SetNextTimeInterval(switch_debounce, Settings.switch_debounce); + SwitchHandler(0); + } + } +} + +#endif // SWITCH_V2 diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index cc0bb5b67a46..3125ef8fa39d 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -317,7 +317,7 @@ bool RuleSetProcess(byte rule_set, String &event_saved) } commands.replace(F("%time%"), String(GetMinutesPastMidnight())); commands.replace(F("%uptime%"), String(GetMinutesUptime())); - commands.replace(F("%timestamp%"), GetDateAndTime(DT_LOCAL).c_str()); + commands.replace(F("%timestamp%"), GetDateAndTime(DT_LOCAL).c_str()); #if defined(USE_TIMERS) && defined(USE_SUNRISE) commands.replace(F("%sunrise%"), String(GetSunMinutes(0))); commands.replace(F("%sunset%"), String(GetSunMinutes(1))); @@ -410,7 +410,7 @@ void RulesEvery50ms(void) if (pin[GPIO_SWT1 +i] < 99) { #endif // USE_TM1638 boolean swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i])); - snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ lastwallswitch[i])); + snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ SwitchLastState(i))); RulesProcessEvent(json_event); } } diff --git a/sonoff/xsns_28_tm1638.ino b/sonoff/xsns_28_tm1638.ino index 61852b40ddda..b446fa97b75f 100644 --- a/sonoff/xsns_28_tm1638.ino +++ b/sonoff/xsns_28_tm1638.ino @@ -176,8 +176,8 @@ void TmLoop(void) if (tm1638_state) { byte buttons = Tm1638GetButtons(); for (byte i = 0; i < MAX_SWITCHES; i++) { - virtualswitch[i] = (buttons &1) ^1; - byte color = (virtualswitch[i]) ? TM1638_COLOR_NONE : TM1638_COLOR_RED; + SwitchSetVirtual(i, (buttons &1) ^1); + byte color = (SwitchGetVirtual(i)) ? TM1638_COLOR_NONE : TM1638_COLOR_RED; Tm1638SetLED(color, i); buttons >>= 1; }