/* xdrv_35_pwm_dimmer.ino - PWM Dimmer Switch support for Tasmota Copyright (C) 2020 Paul C Diem 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 . */ #ifdef USE_PWM_DIMMER /*********************************************************************************************\ * Support for Martin Jerry/acenx/Tessan/NTONPOWER SD0x PWM dimmer switches. The brightness of * the load for these dimmers is controlled by a PWM GPIO pin. Examples are: * * https://www.amazon.com/dp/B07FXYSVR1 * https://www.amazon.com/dp/B07V26Q3VD * https://www.amazon.com/dp/B07K67D43J * https://www.amazon.com/dp/B07TTGFWFM * \*********************************************************************************************/ #define XDRV_35 35 #ifndef MAX_FIXED_COLOR #define MAX_FIXED_COLOR 12 #endif // MAX_FIXED_COLOR // Note: CmndFade and CmndSpeed are in xdrv_04_light. const char kPWMDimmerCommands[] PROGMEM = "|" // No prefix D_CMND_BRI_PRESET "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_FADE "|" D_CMND_SPEED; void (* const PWMDimmerCommand[])(void) PROGMEM = { CmndBriPreset, &PWMDmmerCmndDimmer, &CmndDimmerRange, &CmndFade, &CmndSpeed }; #ifdef USE_PWM_DIMMER_REMOTE struct remote_pwm_dimmer { power_t power; uint32_t dimmer_range; uint8_t light_speed; uint8_t bri_power_on; uint8_t bri_preset_low; uint8_t bri_preset_high; uint8_t fixed_color_index; uint8_t bri; bool power_button_increases_bri; bool light_fade; }; #endif // USE_PWM_DIMMER_REMOTE uint32_t led_timeout_time = 0; uint32_t turn_off_brightness_leds_time = 0; uint32_t button_hold_time[3]; uint8_t current_bri; uint8_t target_bri; uint8_t restore_powered_off_led = 0; uint8_t power_button_index = 0; uint8_t down_button_index = 1; uint8_t up_button_index = 2; uint8_t fixed_color_index; uint8_t button_press_count[3] = { 0, 0, 0 }; bool relay_is_on = false; bool ignore_power_button_hold; bool ignore_power_button_release; bool button_was_held = false; bool power_button_increases_bri = true; bool invert_power_button_bri_direction = false; bool button_hold_sent[3]; bool button_pressed[3] = { false, false, false }; #ifdef USE_PWM_DIMMER_REMOTE struct remote_pwm_dimmer * remote_pwm_dimmers; struct remote_pwm_dimmer * active_remote_pwm_dimmer; uint8_t buttons_pressed = 0; bool active_device_is_local; #endif // USE_PWM_DIMMER_REMOTE void PWMModuleInit() { Settings.seriallog_level = 0; Settings.flag.mqtt_serial = 0; // Disable serial logging Settings.ledstate = 0; // Disable LED usage Settings.flag.pwm_control = 0; // Use basic PWM control instead of Light if (Settings.last_module != Settings.module) { Settings.dimmer_hw_min = 102; Settings.dimmer_hw_max = 1023; Settings.bri_power_on = 50; Settings.bri_preset_low = 25; Settings.bri_preset_high = 255; Settings.last_module = Settings.module; } if (Settings.light_speed < 1) Settings.light_speed = 1; target_bri = ((Settings.power & 1) ? changeUIntScale(Settings.light_dimmer, 0, 100, 0, 255) : 0); current_bri = target_bri; if (pin[GPIO_PWM1] < 99) { uint32_t pwm_value = (current_bri ? changeUIntScale(current_bri, 1, 255, Settings.dimmer_hw_min, Settings.dimmer_hw_max) : 0); analogWrite(pin[GPIO_PWM1], bitRead(pwm_inverted, 0) ? Settings.dimmer_hw_max - pwm_value : pwm_value); } relay_is_on = (current_bri > 0); if (pin[GPIO_REL1] < 99) DigitalWrite(GPIO_REL1, bitRead(rel_inverted, 0) ? !relay_is_on : relay_is_on); PWMDimmerSetPoweredOffLed(); PWMDimmerSetBrightnessLeds(0); } void PWMDimmerInit(void) { #ifdef USE_PWM_DIMMER_REMOTE if (Settings.flag4.remote_device_mode) { if (device_group_count > 1) { if ((remote_pwm_dimmers = (struct remote_pwm_dimmer *) calloc(device_group_count - 1, sizeof(struct remote_pwm_dimmer))) == nullptr) { AddLog_P2(LOG_LEVEL_ERROR, PSTR("PWMDimmer: error allocating PWM dimmer array")); Settings.flag4.remote_device_mode = false; } } } active_device_is_local = true; #endif // USE_PWM_DIMMER_REMOTE } // operation: 0 = normal, -1 = all off, 1 = all on void PWMDimmerSetBrightnessLeds(int32_t operation) { if (leds_present) { uint32_t step = (!operation ? 256 / (leds_present + 1) : operation < 0 ? 255 : 0); uint32_t level = step; SetLedPowerIdx(0, current_bri >= level); if (leds_present > 1) { level += step; SetLedPowerIdx(1, current_bri >= level); if (leds_present > 2) { level += step; SetLedPowerIdx(2, current_bri >= level); if (leds_present > 3) { level += step; SetLedPowerIdx(3, current_bri >= level); } } } // If enabled, set the LED timeout. if (!operation) led_timeout_time = (current_bri && Settings.flag4.led_timeout ? millis() + 5000 : 0); } } void PWMDimmerSetPoweredOffLed(void) { // Set the powered-off LED state. if (pin[GPIO_LEDLNK] < 99) { bool power_off_led_on = !power && Settings.flag4.powered_off_led; if (ledlnk_inverted) power_off_led_on ^= 1; digitalWrite(pin[GPIO_LEDLNK], power_off_led_on); } } void PWMDimmerAnimate(bool no_fade) { // We're only here if something changed. If this is no longer the case, uncomment the following // line. //if (current_bri == target_bri) return; // Advance the current brightness towards the target. if (!no_fade && Settings.light_fade) { uint8_t offset = current_bri / Settings.light_speed / 5 + 1; uint8_t max_offset = abs(target_bri - current_bri); if (offset > max_offset) offset = max_offset; if (current_bri < target_bri) current_bri += offset; else current_bri -= offset; } else { current_bri = target_bri; } // Set the new PWM value. if (pin[GPIO_PWM1] < 99) { uint32_t pwm_value = (current_bri ? changeUIntScale(current_bri, 1, 255, Settings.dimmer_hw_min, Settings.dimmer_hw_max) : 0); //AddLog_P2(LOG_LEVEL_INFO, PSTR("PWMDimmerAnimate: current_bri=%u, pwm_value =%u"), current_bri, pwm_value); analogWrite(pin[GPIO_PWM1], bitRead(pwm_inverted, 0) ? Settings.dimmer_hw_max - pwm_value : pwm_value); } // Handle a power state change. if (relay_is_on != (current_bri > 0)) { bool power_is_on = ((power & 1) != 0); if (power_is_on == relay_is_on) { ExecuteCommandPower(1, (relay_is_on ? POWER_OFF : POWER_ON), SRC_SWITCH); } relay_is_on = !relay_is_on; if (pin[GPIO_REL1] < 99) DigitalWrite(GPIO_REL1, bitRead(rel_inverted, 0) ? !relay_is_on : relay_is_on); // Set the powered-off LED. PWMDimmerSetPoweredOffLed(); } // Set the brightness LED's. PWMDimmerSetBrightnessLeds(0); } void PWMDimmerSetBri(uint8_t bri) { if (bri == target_bri) return; target_bri = bri; Settings.bri_power_on = target_bri; Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT MqttPublishTeleState(); } PWMDimmerAnimate(false); } #ifdef USE_DEVICE_GROUPS void PWMDimmerHandleDeviceGroupRequest() { static bool send_state = false; uint32_t value = XdrvMailbox.payload; #ifdef USE_PWM_DIMMER_REMOTE uint8_t device_group_index = XdrvMailbox.index >> 16 & 0xff; bool device_is_local = device_groups[device_group_index].local; struct remote_pwm_dimmer * remote_pwm_dimmer = &remote_pwm_dimmers[device_group_index]; #endif // USE_PWM_DIMMER_REMOTE switch (XdrvMailbox.command_code) { case DGR_ITEM_EOL: if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) { if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT MqttPublishTeleState(); } send_state = false; } break; case DGR_ITEM_LIGHT_BRI: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) { remote_pwm_dimmer->bri = value; } else #endif // USE_PWM_DIMMER_REMOTE if (target_bri != value) { PWMDimmerSetBri(value); send_state = true; } break; case DGR_ITEM_LIGHT_SPEED: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) { remote_pwm_dimmer->light_speed = value; } else #endif // USE_PWM_DIMMER_REMOTE if (Settings.light_speed != value && value > 0 && value <= 40) { Settings.light_speed = value; send_state = true; } break; case DGR_ITEM_LIGHT_FIXED_COLOR: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) { remote_pwm_dimmer->fixed_color_index = value; } else #endif // USE_PWM_DIMMER_REMOTE fixed_color_index = value; break; case DGR_ITEM_DIMMER_RANGE: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) remote_pwm_dimmer->dimmer_range = value; else { #endif // USE_PWM_DIMMER_REMOTE Settings.dimmer_hw_min = value & 0xffff; Settings.dimmer_hw_max = value >> 16; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE break; case DGR_ITEM_BRI_PRESET_LOW: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) remote_pwm_dimmer->bri_preset_low = value; else #endif // USE_PWM_DIMMER_REMOTE if (Settings.bri_preset_low != value) { Settings.bri_preset_low = value; send_state = true; } break; case DGR_ITEM_BRI_PRESET_HIGH: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) remote_pwm_dimmer->bri_preset_high = value; else #endif // USE_PWM_DIMMER_REMOTE if (Settings.bri_preset_high != value) { Settings.bri_preset_high = value; send_state = true; } break; case DGR_ITEM_BRI_POWER_ON: #ifdef USE_PWM_DIMMER_REMOTE if (!device_is_local) remote_pwm_dimmer->bri_power_on = value; else #endif // USE_PWM_DIMMER_REMOTE if (Settings.bri_power_on != value) { Settings.bri_power_on = value; send_state = true; } break; case DGR_ITEM_STATUS: #ifdef USE_PWM_DIMMER_REMOTE if (device_is_local) #endif // USE_PWM_DIMMER_REMOTE SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade, DGR_ITEM_LIGHT_SPEED, Settings.light_speed, DGR_ITEM_LIGHT_BRI, target_bri, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high, DGR_ITEM_DIMMER_RANGE, Settings.dimmer_hw_min | Settings.dimmer_hw_max << 16, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on); break; } } #endif // USE_DEVICE_GROUPS void PWMDimmerHandleButton() { /* * Power Button Up/Down Buttons State Remote Mode Action * -------------------- ---------------------- ----- ------------ ---------------------------- * Press & release Released Any Any Toggle power * Hold for hold time Released On Any Brighter/dimmer while held * then reverse direction * Hold for hold time Released Off Any Power on at bri preset low * Hold while Press & release up Any Any Toggle/change options * Hold while Press & release down Any Any Toggle/change options * Hold while Press up On Yes Brighter * Hold while Press down On Yes Dimmer * Released Press up On No Brigther * Released Press down On No Dimmer * Released Press & release up Off No Power on at bri preset low * Released Press & release down Off No Power on at bri preset high * * Holding any button for over 10 seconds executes the WiFiConfig 2 command. * * In remote mode, whichever button is pressed first becomes the power button and any buttons * pressed while it is held affect the device associated with it. The up and down buttons change * depeneding on which button is the current power button: * * Power Down Up * ----- ---- -- * 1 2 3 * 2 1 3 * 3 1 2 */ uint32_t button_index = XdrvMailbox.index; uint32_t now = millis(); bool toggle_power = false; uint8_t dgr_item = 0; uint8_t dgr_value; // If the button is pressed, ... if (!XdrvMailbox.payload) { int8_t bri_direction = 0; // If the button was just pressed, reset the press count if it was released for longer than 1 // second, flag the button as pressed, clear the hold sent flag and increment the buttons // pressed count. if (!button_pressed[button_index]) { if (now > button_hold_time[button_index] && now - button_hold_time[button_index] > 1000) button_press_count[button_index] = 0; button_pressed[button_index] = true; button_hold_sent[button_index] = false; #ifdef USE_PWM_DIMMER_REMOTE buttons_pressed++; // If there are no other buttons pressed right now and remote mode is enabled, make the device // associated with this button the device we're going to control. if (buttons_pressed == 1 && Settings.flag4.remote_device_mode) { power_button_index = button_index; up_button_index = (button_index == 2 ? 1 : 2); down_button_index = (button_index ? 0 : 1); active_device_is_local = device_groups[power_button_index].local; if (!active_device_is_local) active_remote_pwm_dimmer = &remote_pwm_dimmers[power_button_index - 1]; } if (button_index == power_button_index) { #else // USE_PWM_DIMMER_REMOTE // If this is about the power button, initialize some variables. if (!button_index) { #endif // USE_PWM_DIMMER_REMOTE button_hold_time[button_index] = now + 500; ignore_power_button_hold = false; ignore_power_button_release = false; return; } // If this is not about the power button, load the new hold time. Note that the hold time for // the power button is longer than the hold time for the other buttons. button_hold_time[button_index] = now + 100; } // If the button is being held, send a button hold. else if (button_hold_time[button_index] < now) { if (!button_hold_sent[button_index]) { button_hold_sent[button_index] = true; SendKey(KEY_BUTTON, button_index + 1, POWER_HOLD); } // If the button has been held for over 10 seconds, execute the WiFiConfig 2 command. if (now - button_hold_time[button_index] > 10000) { char scmnd[20]; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " 2")); ExecuteCommand(scmnd, SRC_BUTTON); return; } } // If this is about the power button, ... #ifdef USE_PWM_DIMMER_REMOTE if (button_index == power_button_index) { #else // USE_PWM_DIMMER_REMOTE if (!button_index) { #endif // USE_PWM_DIMMER_REMOTE // If the power button has been held with no other buttons pressed, ... if (!ignore_power_button_hold && button_hold_time[button_index] < now) { ignore_power_button_release = true; // If the device power is on, adjust the brightness. Set the direction based on the current // direction for the device and then invert the direction when the power button is released. // The new brightness will be calculated below. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local ? active_remote_pwm_dimmer->power : power) { bri_direction = (!active_device_is_local ? (active_remote_pwm_dimmer->power_button_increases_bri ? 1 : -1) : (power_button_increases_bri ? 1 : -1)); #else // USE_PWM_DIMMER_REMOTE if (power) { bri_direction = (power_button_increases_bri ? 1 : -1); #endif // USE_PWM_DIMMER_REMOTE invert_power_button_bri_direction = true; } // If the power is not on, turn it on using an initial brightness of bri_preset_low and set // the power button hold dimmer direction to true so holding the power switch increases the // brightness. else { #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_preset_low; active_remote_pwm_dimmer->power_button_increases_bri = true; } else { #endif // USE_PWM_DIMMER_REMOTE target_bri = Settings.bri_preset_low; power_button_increases_bri = true; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE toggle_power = true; // Reset the power button hold time to delay before we start increasing the brightness. button_hold_time[button_index] = now + 500; } } } // If this is about the down or up buttons, ... else { // If the power button is also pressed, set flags to ignore the power button being held and // the next power button release. if (button_pressed[power_button_index]) { ignore_power_button_release = ignore_power_button_hold = true; } // If the active device is local or the power button if also pressed, ... #ifdef USE_PWM_DIMMER_REMOTE if (active_device_is_local == !button_pressed[power_button_index]) { #endif // USE_PWM_DIMMER_REMOTE bool is_down_button = (button_index == down_button_index); // If the power is not on, turn it on using a temporary brightness of bri_preset_low if the // down button is pressed or bri_preset_low if the up button is pressed. #ifdef USE_PWM_DIMMER_REMOTE if ((!active_device_is_local ? !active_remote_pwm_dimmer->power : !power)) { #else // USE_PWM_DIMMER_REMOTE if (!power) { #endif // USE_PWM_DIMMER_REMOTE #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->bri = (is_down_button ? active_remote_pwm_dimmer->bri_preset_low : active_remote_pwm_dimmer->bri_preset_high); else #endif // USE_PWM_DIMMER_REMOTE target_bri = (is_down_button ? Settings.bri_preset_low : Settings.bri_preset_high); toggle_power = true; button_hold_time[button_index] = now + 500; // If the power button is also pressed, set the power button hold dimmer direction // so holding the power switch adjusts the brightness away from the brightness we // just set. #ifdef USE_PWM_DIMMER_REMOTE if (button_pressed[power_button_index]) active_remote_pwm_dimmer->power_button_increases_bri = is_down_button; #endif // USE_PWM_DIMMER_REMOTE } // If the power is on and the down/up button has been held, handle the action based on the // number of times the down or up button was pressed and released before holding it. else if (button_hold_time[button_index] < now) { uint8_t uint8_value; bool is_down_button = (button_index == down_button_index); bool down_button_was_tapped = (button_press_count[down_button_index] > 0); uint8_t tap_count = (down_button_was_tapped ? button_press_count[down_button_index] : button_press_count[up_button_index]); uint16_t uint16_value; switch (tap_count) { case 0: // Adjust the brightness. Set the direction based on which button is pressed. The new // brightness will be calculated below. bri_direction = (is_down_button ? -1 : 1); break; case 1: if (down_button_was_tapped) { // Select the previous/next color. #ifdef USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) uint8_value = active_remote_pwm_dimmer->fixed_color_index; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = fixed_color_index; if (is_down_button) { if (uint8_value) uint8_value--; else uint8_value = MAX_FIXED_COLOR; } else { if (uint8_value < MAX_FIXED_COLOR) uint8_value++; else uint8_value = 0; } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->fixed_color_index = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE fixed_color_index = uint8_value; dgr_item = DGR_ITEM_LIGHT_FIXED_COLOR; dgr_value = uint8_value; #endif // USE_DEVICE_GROUPS ; } else { // Publish MQTT Event SwitchTrigger#. char topic[TOPSZ]; #ifdef USE_PWM_DIMMER_REMOTE snprintf_P(topic, sizeof(topic), PSTR("%s/cmnd/Event"), device_groups[power_button_index].group_name); #else // USE_PWM_DIMMER_REMOTE snprintf_P(topic, sizeof(topic), PSTR("%s/cmnd/Event"), SettingsText(SET_MQTT_GRP_TOPIC)); #endif // USE_PWM_DIMMER_REMOTE sprintf_P(mqtt_data, PSTR("SwitchTrigger%u"), (is_down_button ? 1 : 2)); MqttPublish(topic); } break; case 2: if (down_button_was_tapped) { // Decrease/increase the minimum brightness. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) uint16_value = active_remote_pwm_dimmer->dimmer_range & 0xffff; else #endif // USE_PWM_DIMMER_REMOTE uint16_value = Settings.dimmer_hw_min; if (is_down_button) { if (uint16_value > 0) uint16_value--; } else if (uint16_value < 65535) { uint16_value++; } #ifdef USE_PWM_DIMMER_REMOTE dgr_item = DGR_ITEM_DIMMER_RANGE; if (!active_device_is_local) { active_remote_pwm_dimmer->dimmer_range &= 0xffff0000 | uint16_value; dgr_value = active_remote_pwm_dimmer->dimmer_range; } else { #endif // USE_PWM_DIMMER_REMOTE Settings.dimmer_hw_min = uint16_value; dgr_value = Settings.dimmer_hw_min | Settings.dimmer_hw_max << 16; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } else { // Decrease/increase the fade speed. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) uint8_value = active_remote_pwm_dimmer->light_speed; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = Settings.light_speed; if (is_down_button) { if (uint8_value > 1) uint8_value--; } else if (uint8_value < 40) { uint8_value++; } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->light_speed = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE Settings.light_speed = uint8_value; dgr_item = DGR_ITEM_LIGHT_SPEED; dgr_value = uint8_value; } break; case 3: if (down_button_was_tapped) { // Decrease/increase the low brightness preset. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) uint8_value = active_remote_pwm_dimmer->bri_preset_low; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = Settings.bri_preset_low; if (is_down_button) { if (uint8_value > 3) uint8_value--; } else if (uint8_value < 255) { uint8_value++; } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->bri_preset_low = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_low = uint8_value; dgr_item = DGR_ITEM_BRI_PRESET_LOW; dgr_value = uint8_value; } else { // Decrease/increase the high brightness preset. #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) uint8_value = active_remote_pwm_dimmer->bri_preset_high; else #endif // USE_PWM_DIMMER_REMOTE uint8_value = Settings.bri_preset_high; if (is_down_button) { if (uint8_value > 3) uint8_value--; } else if (uint8_value < 255) { uint8_value++; } #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->bri_preset_high = uint8_value; else #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_high = uint8_value; dgr_item = DGR_ITEM_BRI_PRESET_HIGH; dgr_value = uint8_value; } break; } button_was_held = true; // If the button was tapped before it was held, reset the button hold time to 1/2 second // and turn all the brightness LEDs on for 250ms. if (tap_count > 0) { button_hold_time[button_index] = now + 500; turn_off_brightness_leds_time = now + 250; PWMDimmerSetBrightnessLeds(1); } } #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } // If we need to adjust the brightness, do it. The brightness is adjusted faster the longer the // button is held. if (bri_direction) { int32_t bri; #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) bri = active_remote_pwm_dimmer->bri; else #endif // USE_PWM_DIMMER_REMOTE bri = target_bri; int32_t new_bri; int32_t offset = bri / 12 + 1; if (bri_direction > 0) { new_bri = bri + offset; if (new_bri > 255) new_bri = 255; } else { new_bri = bri - offset; if (new_bri < 3) new_bri = 3; } if (new_bri != bri) { #ifdef USE_DEVICE_GROUPS SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_ITEM_LIGHT_BRI, new_bri); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri; else { #endif // USE_PWM_DIMMER_REMOTE Settings.bri_power_on = target_bri = new_bri; Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); PWMDimmerAnimate(true); #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } } } // If the button was just released, ... else if (button_pressed[button_index]) { // If the button was held, send a button off; otherwise, send a button toggle. SendKey(KEY_BUTTON, button_index + 1, (button_hold_sent[button_index] ? POWER_OFF : POWER_TOGGLE)); // If this is about the power button, ... #ifdef USE_PWM_DIMMER_REMOTE if (button_index == power_button_index) { #else // USE_PWM_DIMMER_REMOTE if (!button_index) { #endif // USE_PWM_DIMMER_REMOTE // If we're ignoring the next power button released, ... if (ignore_power_button_release) { ignore_power_button_release = false; // If the power button was held with no other buttons pressed, we changed the brightness so // invert the bri direction for the next time and send a final update. if (invert_power_button_bri_direction) { invert_power_button_bri_direction = false; dgr_item = DGR_ITEM_LIGHT_BRI; #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { active_remote_pwm_dimmer->power_button_increases_bri ^= 1; dgr_value = active_remote_pwm_dimmer->bri; } else { #endif // USE_PWM_DIMMER_REMOTE power_button_increases_bri ^= 1; dgr_value = target_bri; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } // The up and/or down buttons were pressed while the power button was pressed. Handle the // options based on the number of times the buttons were pressed. else { switch (button_press_count[down_button_index]) { case 1: // Toggle the powered-off LED option. #ifdef USE_PWM_DIMMER_REMOTE if (active_device_is_local) { #endif // USE_PWM_DIMMER_REMOTE Settings.flag4.powered_off_led ^= 1; PWMDimmerSetPoweredOffLed(); #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE break; case 2: // Toggle fading. dgr_item = DGR_ITEM_LIGHT_FADE; #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { active_remote_pwm_dimmer->light_fade ^= 1; dgr_value = active_remote_pwm_dimmer->light_fade; } else { #endif // USE_PWM_DIMMER_REMOTE Settings.light_fade ^= 1; dgr_value = Settings.light_fade; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE break; } button_press_count[down_button_index] = 0; switch (button_press_count[up_button_index]) { case 1: // Toggle the LED timeout. #ifdef USE_PWM_DIMMER_REMOTE if (active_device_is_local) { #endif // USE_PWM_DIMMER_REMOTE Settings.flag4.led_timeout ^= 1; if (relay_is_on) PWMDimmerSetBrightnessLeds(Settings.flag4.led_timeout ? -1 : 0); #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE break; } button_press_count[up_button_index] = 0; } } // If we're not ignoring the power button until it's released, toggle the power. else { #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_power_on; else #endif // USE_PWM_DIMMER_REMOTE target_bri = Settings.bri_power_on; toggle_power = true; } } // If this is about the up or down buttons, ... else { // If the button was held, ... if (button_was_held) { button_was_held = false; // If the button was tapped before it was held, we used the brightness LEDs to inidcate the // operation so reset the brightness LEDs. if (button_press_count[down_button_index] > 0 || button_press_count[up_button_index]) { turn_off_brightness_leds_time = 0; PWMDimmerSetBrightnessLeds(0); } // If the button was not tapped before it was held, we changed the brightness and sent // updates with the more-to-come message type. Send a final update. else { dgr_item = DGR_ITEM_LIGHT_BRI; #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) dgr_value = active_remote_pwm_dimmer->bri; else #endif // USE_PWM_DIMMER_REMOTE dgr_value = target_bri; } // Reset the the button press count. button_press_count[button_index] = 0; } // If the button was tapped (pressed and released quickly), increment the count of how many // times this button has been pressed. else if (button_hold_time[button_index] >= now) { button_press_count[button_index]++; } } // Flag the button as released. button_pressed[button_index] = false; #ifdef USE_PWM_DIMMER_REMOTE buttons_pressed--; #endif // USE_PWM_DIMMER_REMOTE } if (toggle_power) { power_t new_power; uint8_t new_bri; #ifdef USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) { active_remote_pwm_dimmer->power ^= 1; new_power = active_remote_pwm_dimmer->power; new_bri = active_remote_pwm_dimmer->bri; } else { #endif // USE_PWM_DIMMER_REMOTE new_power = power ^ 1; new_bri = target_bri; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE if (new_power) SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, new_bri, DGR_ITEM_POWER, new_power); else SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, new_power); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE // The target brightness has already been set. Execute the toggle power command with a source of // SRC_RETRY. This will turn the power on using the current target brightness (see // FUNC_SET_DEVICE_POWER below). if (active_device_is_local) #endif // USE_PWM_DIMMER_REMOTE ExecuteCommandPower(1, POWER_TOGGLE, SRC_RETRY); } // If we're not toggling the power and we made changes, send a group update. else if (dgr_item) { #ifdef USE_DEVICE_GROUPS SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE_DIRECT, dgr_item, dgr_value); #endif // USE_DEVICE_GROUPS if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT MqttPublishTeleState(); } } if (turn_off_brightness_leds_time && turn_off_brightness_leds_time < millis()) { turn_off_brightness_leds_time = 0; PWMDimmerSetBrightnessLeds(-1); } } /*********************************************************************************************\ * Commands \*********************************************************************************************/ void CmndBriPreset(void) { if (XdrvMailbox.data_len > 0) { bool valid = true; uint32_t value; uint8_t parm[2]; parm[0] = Settings.bri_preset_low; parm[1] = Settings.bri_preset_high; char * ptr = XdrvMailbox.data; for (uint32_t i = 0; i < 2; i++) { while (*ptr == ' ') ptr++; if (*ptr == '+') { if (parm[i] < 255) parm[i]++; } else if (*ptr == '-') { if (parm[i] > 0) parm[i]--; } else { value = strtoul(ptr, &ptr, 0); if (value < 1 || parm[i] > 255) { valid = false; break; } parm[i] = value; if (*ptr != ',') break; } ptr++; } if (valid && !*ptr) { if (parm[0] < parm[1]) { Settings.bri_preset_low = parm[0]; Settings.bri_preset_high = parm[1]; } else { Settings.bri_preset_low = parm[1]; Settings.bri_preset_high = parm[0]; } #ifdef USE_DEVICE_GROUPS SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high); #endif // USE_DEVICE_GROUPS } } Response_P(PSTR("{\"" D_CMND_BRI_PRESET "\":{\"Low\":%d,\"High\":%d}}"), Settings.bri_preset_low, Settings.bri_preset_high); } void PWMDmmerCmndDimmer(void) { uint8_t dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); if (1 == XdrvMailbox.data_len) { if ('+' == XdrvMailbox.data[0]) XdrvMailbox.payload = (dimmer > 89) ? 100 : dimmer + 10; else if ('-' == XdrvMailbox.data[0]) { XdrvMailbox.payload = (dimmer < 11) ? 1 : dimmer - 10; } } if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 100) { uint8_t bri = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255); #ifdef USE_DEVICE_GROUPS SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, bri); #endif // USE_DEVICE_GROUPS PWMDimmerSetBri(bri); } ResponseCmndNumber(Settings.light_dimmer); } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv35(uint8_t function) { bool result = false; if (PWM_DIMMER != my_module_type) return result; switch (function) { case FUNC_LOOP: if (current_bri != target_bri) PWMDimmerAnimate(false); break; case FUNC_EVERY_SECOND: // Turn off the brightness LED's if it's time. if (led_timeout_time && led_timeout_time < millis()) { for (uint32_t index = 0; index < leds_present; index++) SetLedPowerIdx(index, 0); led_timeout_time = 0; } // The powered-off LED is also the LedLink LED. If we lose the WiFi or MQTT server // connection, the LED will be set to a blinking state and will be turned off when the // connection is restored. If the state is blinking now, set a flag so we know that we need // to restore it when it stops blinking. if (global_state.data) restore_powered_off_led = 5; else if (restore_powered_off_led) { PWMDimmerSetPoweredOffLed(); restore_powered_off_led--; } break; case FUNC_BUTTON_PRESSED: // Handle the button press/release instead of the button handler. PWMDimmerHandleButton(); result = true; break; #ifdef USE_DEVICE_GROUPS case FUNC_DEVICE_GROUP_REQUEST: PWMDimmerHandleDeviceGroupRequest(); break; #endif // USE_DEVICE_GROUPS case FUNC_SET_DEVICE_POWER: // Handle turning the power on/off here so we can fade off. Set the target brightness and let // Animate handle turning the power off/on. If the source is SRC_RETRY, we're turning the // power on with a brightness preset and the target brightness has already been set. if (!XdrvMailbox.index) target_bri = 0; else if (SRC_RETRY != XdrvMailbox.payload) { target_bri = Settings.bri_power_on; Settings.light_dimmer = changeUIntScale(target_bri, 0, 255, 0, 100); } result = true; break; case FUNC_COMMAND: result = DecodeCommand(kPWMDimmerCommands, PWMDimmerCommand); break; case FUNC_INIT: PWMDimmerInit(); break; case FUNC_PRE_INIT: #ifdef USE_PWM_DIMMER_REMOTE // If remote device mode is enabled, set the device group count to the number of buttons // present. if (Settings.flag4.remote_device_mode) { for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { if (pin[GPIO_KEY1 + button_index] < 99) device_group_count++; } } #endif // USE_PWM_DIMMER_REMOTE break; case FUNC_MODULE_INIT: PWMModuleInit(); result = true; break; } return result; } #endif // USE_PWM_DIMMER