/* xdrv_35_pwm_dimmer.ino - PWM Dimmer Switch support for Tasmota Copyright (C) 2021 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 #define MAX_PWM_DIMMER_KEYS 3 const char kPWMDimmerCommands[] PROGMEM = "|" // No prefix D_CMND_BRI_PRESET #ifdef USE_DEVICE_GROUPS "|" D_CMND_PWM_DIMMER_PWMS #endif // USE_DEVICE_GROUPS ; void (* const PWMDimmerCommand[])(void) PROGMEM = { &CmndBriPreset, #ifdef USE_DEVICE_GROUPS &CmndPWMDimmerPWMs, #endif // USE_DEVICE_GROUPS }; #ifdef USE_PWM_DIMMER_REMOTE struct remote_pwm_dimmer { bool power_on; 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; }; #endif // USE_PWM_DIMMER_REMOTE uint32_t ignore_any_key_time = 0; uint32_t button_hold_time[3]; uint8_t led_timeout_seconds = 0; uint8_t restore_powered_off_led_counter = 0; uint8_t power_button_index = 0; uint8_t down_button_index = 1; uint8_t buttons_pressed = 0; uint8_t local_fixed_color_index = 128; bool button_tapped = false; bool down_button_tapped = false; bool ignore_power_button = false; bool multibutton_in_progress = false; bool power_button_increases_bri = true; bool tap_handled = false; bool invert_power_button_bri_direction = false; bool button_pressed[3] = { false, false, false }; bool button_held[3]; bool button_unprocessed[3] = { false, false, false }; #ifdef USE_PWM_DIMMER_REMOTE struct remote_pwm_dimmer remote_pwm_dimmers[MAX_PWM_DIMMER_KEYS]; struct remote_pwm_dimmer * active_remote_pwm_dimmer; #endif // USE_PWM_DIMMER_REMOTE void PWMModulePreInit(void) { Settings.seriallog_level = 0; Settings.flag.mqtt_serial = 0; // Disable serial logging Settings.ledstate = 0; // Disable LED usage // If the module was just changed to PWM Dimmer, set the defaults. if (TasmotaGlobal.module_changed) { Settings.flag.pwm_control = true; // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL Settings.bri_power_on = Settings.bri_preset_low = Settings.bri_preset_high = 0; } // Previous versions of PWM Dimmer used SetOption32 - Button held for factor times longer as the // hold time. The hold time is now fixed and SetOption32 is used as normal including to // determine how long a button is held before a reset command is executed. If SetOption32 is // still 5, change it to 40 (the default). if (Settings.param[P_HOLD_TIME] == 5) Settings.param[P_HOLD_TIME] = 40; // Make sure the brightness level settings are sensible. if (!Settings.bri_power_on) Settings.bri_power_on = 128; if (!Settings.bri_preset_low) Settings.bri_preset_low = 10; if (Settings.bri_preset_high < Settings.bri_preset_low) Settings.bri_preset_high = 255; PWMDimmerSetPoweredOffLed(); // The relay initializes to on. If the power is supposed to be off, turn the relay off. // if (!TasmotaGlobal.power && PinUsed(GPIO_REL1)) digitalWrite(Pin(GPIO_REL1), bitRead(TasmotaGlobal.rel_inverted, 0) ? 1 : 0); #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.multiple_device_groups) { Settings.flag4.device_groups_enabled = true; device_group_count = 0; for (uint32_t button_index = 0; button_index < MAX_PWM_DIMMER_KEYS; button_index++) { if (PinUsed(GPIO_KEY1, button_index)) device_group_count++; } // If no relay or PWM is defined, all buttons control remote devices. if (!PinUsed(GPIO_REL1) && !PinUsed(GPIO_PWM1)) { first_device_group_is_local = false; // Back out the changes made in the light module under the assumtion we have a relay or PWM. TasmotaGlobal.devices_present--; TasmotaGlobal.light_type = 0; } for (uint8_t i = 0; i < MAX_PWM_DIMMER_KEYS; i++) { active_remote_pwm_dimmer = &remote_pwm_dimmers[i]; active_remote_pwm_dimmer->bri_power_on = 128; active_remote_pwm_dimmer->bri_preset_low = 10; active_remote_pwm_dimmer->bri_preset_high = 255; active_remote_pwm_dimmer->fixed_color_index = 128; } } #endif // USE_PWM_DIMMER_REMOTE } // bri: -1 = set to current light bri, -2 = timeout, 0-255 = set to bri void PWMDimmerSetBrightnessLeds(int32_t bri) { // Find out how many of the LEDs have their ledmask bit set. uint32_t leds = 0; uint32_t mask = 1; int32_t led; for (led = 0; led < TasmotaGlobal.leds_present; led++) { if (Settings.ledmask & mask) leds++; mask <<= 1; } // If we found at least one LED, get the brightness to show and calculate the brightness level // difference between each LED. if (leds) { led_timeout_seconds = 5; if (bri < 0) { bri = ((bri == -2 && Settings.flag4.led_timeout) || !Light.power ? 0 : light_state.getBri()); if (!bri || !Settings.flag4.led_timeout) led_timeout_seconds = 0; } uint32_t step = 256 / (leds + 1); // Turn the LED's on/off. uint32_t level = 0; led = -1; mask = 0; for (uint32_t count = 0; count < leds; count++) { level += step; for (;;) { led++; mask <<= 1; if (!mask) mask = 1; if (Settings.ledmask & mask) break; } SetLedPowerIdx(led, bri >= level); } } } void PWMDimmerSetPoweredOffLed(void) { // Set the powered-off LED state. if (PinUsed(GPIO_LEDLNK)) { bool power_off_led_on = !TasmotaGlobal.power && Settings.flag4.powered_off_led; if (TasmotaGlobal.ledlnk_inverted) power_off_led_on ^= 1; digitalWrite(Pin(GPIO_LEDLNK), power_off_led_on); } } void PWMDimmerSetPower(void) { DigitalWrite(GPIO_REL1, 0, bitRead(TasmotaGlobal.rel_inverted, 0) ? !TasmotaGlobal.power : TasmotaGlobal.power); PWMDimmerSetBrightnessLeds(-1); PWMDimmerSetPoweredOffLed(); } #ifdef USE_DEVICE_GROUPS void PWMDimmerHandleDevGroupItem(void) { #ifdef USE_PWM_DIMMER_REMOTE uint8_t device_group_index = *(uint8_t *)XdrvMailbox.topic; bool is_local = ((XdrvMailbox.index & DGR_FLAG_LOCAL) != 0); if (device_group_index > MAX_PWM_DIMMER_KEYS) return; struct remote_pwm_dimmer * remote_pwm_dimmer = &remote_pwm_dimmers[device_group_index]; #else // USE_PWM_DIMMER_REMOTE if (!(XdrvMailbox.index & DGR_FLAG_LOCAL)) return; #endif // !USE_PWM_DIMMER_REMOTE uint32_t value = XdrvMailbox.payload; switch (XdrvMailbox.command_code) { #ifdef USE_PWM_DIMMER_REMOTE case DGR_ITEM_LIGHT_BRI: remote_pwm_dimmer->bri = value; break; case DGR_ITEM_POWER: remote_pwm_dimmer->power_on = value & 1; remote_pwm_dimmer->power_button_increases_bri = (remote_pwm_dimmer->bri < 128); break; #endif // USE_PWM_DIMMER_REMOTE case DGR_ITEM_LIGHT_FIXED_COLOR: #ifdef USE_PWM_DIMMER_REMOTE remote_pwm_dimmer->fixed_color_index = value; if (is_local) #endif // USE_PWM_DIMMER_REMOTE local_fixed_color_index = value; break; case DGR_ITEM_BRI_POWER_ON: #ifdef USE_PWM_DIMMER_REMOTE remote_pwm_dimmer->bri_power_on = value; if (is_local) #endif // USE_PWM_DIMMER_REMOTE Settings.bri_power_on = value; break; case DGR_ITEM_BRI_PRESET_LOW: #ifdef USE_PWM_DIMMER_REMOTE remote_pwm_dimmer->bri_preset_low = value; if (is_local) #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_low = value; break; case DGR_ITEM_BRI_PRESET_HIGH: #ifdef USE_PWM_DIMMER_REMOTE remote_pwm_dimmer->bri_preset_high = value; if (is_local) #endif // USE_PWM_DIMMER_REMOTE Settings.bri_preset_high = value; break; case DGR_ITEM_STATUS: #ifdef USE_PWM_DIMMER_REMOTE if (is_local) #endif // USE_PWM_DIMMER_REMOTE SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on, DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high); #ifdef USE_PWM_DIMMER_REMOTE else SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, remote_pwm_dimmer->power_on, DGR_ITEM_BRI_POWER_ON, remote_pwm_dimmer->bri_power_on, DGR_ITEM_BRI_PRESET_LOW, remote_pwm_dimmer->bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, remote_pwm_dimmer->bri_preset_high); #endif // USE_PWM_DIMMER_REMOTE break; } } #endif // USE_DEVICE_GROUPS void PWMDimmerHandleButton(uint32_t button_index, bool pressed) { bool handle_tap = false; bool state_updated = false; int32_t bri_offset = 0; uint8_t power_on_bri = 0; uint8_t dgr_item = 0; uint8_t dgr_value = 0; uint8_t dgr_more_to_come = true; uint8_t mqtt_trigger = 0; // Initialize some variables. #ifdef USE_PWM_DIMMER_REMOTE bool power_is_on = (active_remote_pwm_dimmer ? active_remote_pwm_dimmer->power_on : TasmotaGlobal.power); bool is_power_button = (button_index == power_button_index); bool is_down_button = (button_index == down_button_index); #else // USE_PWM_DIMMER_REMOTE bool power_is_on = TasmotaGlobal.power; bool is_power_button = !button_index; bool is_down_button = (button_index == (power_button_index ? 0 : 1)); #endif // USE_PWM_DIMMER_REMOTE // If the button is being held, ... if (pressed) { uint32_t now = millis(); // If the button was pressed and released but was not processed by support_button because the // button interval had not elapsed, if (button_unprocessed[button_index]) { mqtt_trigger = 5; #ifdef USE_PWM_DIMMER_REMOTE if (!active_remote_pwm_dimmer) mqtt_trigger += button_index; #endif // USE_PWM_DIMMER_REMOTE button_hold_time[button_index] = now + 750; } // Otherwise, if this is about the power button, ... else if (is_power_button) { // If we're not ignoring the power button and no other buttons are pressed, ... if (!ignore_power_button && buttons_pressed == 1) { // If the 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. if (power_is_on) { #ifdef USE_PWM_DIMMER_REMOTE bri_offset = (active_remote_pwm_dimmer ? (active_remote_pwm_dimmer->power_button_increases_bri ? 1 : -1) : (power_button_increases_bri ? 1 : -1)); #else // USE_PWM_DIMMER_REMOTE bri_offset = (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 time to delay before we start increasing the brightness. else { #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) power_on_bri = active_remote_pwm_dimmer->bri = active_remote_pwm_dimmer->bri_preset_low; else #endif // USE_PWM_DIMMER_REMOTE power_on_bri = Settings.bri_preset_low; button_hold_time[button_index] = now + 500; } } } // If this is about the down or up buttons, ... else { // If the power button is also pressed, ... if (button_pressed[power_button_index]) { // If the up or down button was tapped while holding the power button before this, handle // the operation below. if (button_tapped) { handle_tap = ignore_power_button = true; button_hold_time[button_index] = now + 750; } // Otherwise, if the power is on and remote mode is enabled, adjust the brightness. Set the // direction based on which button is pressed. The new brightness will be calculated below. else if (power_is_on && Settings.flag4.multiple_device_groups) { bri_offset = (is_down_button ? -1 : 1); } // Otherwise, publish MQTT Event Trigger#. else { mqtt_trigger = (is_down_button ? 1 : 2); button_hold_time[button_index] = now + 60000; ignore_power_button = true; } } // Otherwise, if the power is on, adjust the brightness. Set the direction based on which // button is pressed. The new brightness will be calculated below. else if (power_is_on && !button_tapped) { bri_offset = (is_down_button ? -1 : 1); } } } // If the button was just released, ... else { bool button_was_held = button_held[button_index]; // If this is about the power button, ... if (is_power_button) { // If the up or down button was tapped while the power button was held and the up or down // buttons weren't tapped or held afterwards, handle the operation based on which button was // tapped. if (button_tapped) { if (!tap_handled) { #ifdef USE_PWM_DIMMER_REMOTE if (!active_remote_pwm_dimmer) { #endif // USE_PWM_DIMMER_REMOTE // Toggle the powered-off LED option. if (down_button_tapped) { Settings.flag4.led_timeout ^= 1; if (Light.power) PWMDimmerSetBrightnessLeds(Settings.flag4.led_timeout ? 0 : -1); } // Toggle the LED timeout. else { Settings.flag4.powered_off_led ^= 1; PWMDimmerSetPoweredOffLed(); } #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } tap_handled = false; } // Otherwise, if the power button was held, ... else if (button_was_held) { // 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; #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) active_remote_pwm_dimmer->power_button_increases_bri ^= 1; else #endif // USE_PWM_DIMMER_REMOTE power_button_increases_bri ^= 1; #ifdef USE_PWM_DIMMER_REMOTE dgr_item = DGR_ITEM_FLAGS; state_updated = true; #endif // USE_PWM_DIMMER_REMOTE } } // If the power button was not held and we're not ignoring the next power button release, // toggle the power. else if (!ignore_power_button) { #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) power_on_bri = active_remote_pwm_dimmer->bri_power_on; else #endif // USE_PWM_DIMMER_REMOTE power_on_bri = Settings.bri_power_on; } } // If this is about the up or down buttons, ... else { // If the power button is also pressed, set the flag to ignore the power button until it is // released. if (button_pressed[power_button_index]) { ignore_power_button = true; // If the button was tapped, handle it below. if (button_tapped) { handle_tap = true; } // Otherwise, if the button was not held, flag the tap. else if (!button_was_held) { button_tapped = true; down_button_tapped = is_down_button; } } // If the power button is not also pressed, ... else { // If the power is on, ... if (power_is_on) { // If the button was not held, adjust the brightness. Set the direction based on which // button is pressed. The new brightness will be calculated below. if (!button_was_held) { bri_offset = (is_down_button ? -5 : 5); dgr_more_to_come = false; state_updated = true; } // If the button was held, we changed the brightness and sent updates with the // more-to-come message type while the button was held. Send a final update. else { dgr_item = DGR_ITEM_FLAGS; state_updated = true; } } // If the power is off, 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. else { #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) power_on_bri = 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 power_on_bri = (is_down_button ? Settings.bri_preset_low : Settings.bri_preset_high); } } } } // If we need to adjust the brightness, do it. if (bri_offset) { int32_t bri; #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) bri = active_remote_pwm_dimmer->bri; else #endif // USE_PWM_DIMMER_REMOTE bri = light_state.getBri(); int32_t new_bri = bri + bri_offset * (Settings.light_correction ? 4 : bri / 16 + 1); if (bri_offset > 0) { if (new_bri > 255) new_bri = 255; } else { if (new_bri < 1) new_bri = 1; } if (new_bri != bri) { #ifdef USE_DEVICE_GROUPS SendDeviceGroupMessage(power_button_index, (dgr_more_to_come ? DGR_MSGTYP_UPDATE_MORE_TO_COME : DGR_MSGTYP_UPDATE_DIRECT), DGR_ITEM_LIGHT_BRI, new_bri); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) { active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri; PWMDimmerSetBrightnessLeds(new_bri); } else { #endif // USE_PWM_DIMMER_REMOTE TasmotaGlobal.skip_light_fade = true; #ifdef USE_DEVICE_GROUPS ignore_dgr_sends = true; #endif // USE_DEVICE_GROUPS light_state.setBri(new_bri); Settings.light_dimmer = light_state.BriToDimmer(new_bri); LightAnimate(); TasmotaGlobal.skip_light_fade = false; #ifdef USE_DEVICE_GROUPS ignore_dgr_sends = false; #endif // USE_DEVICE_GROUPS Settings.bri_power_on = new_bri; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } #ifdef USE_PWM_DIMMER_REMOTE else if (active_remote_pwm_dimmer) PWMDimmerSetBrightnessLeds(new_bri); #endif // USE_PWM_DIMMER_REMOTE else PWMDimmerSetBrightnessLeds(-1); } // If we need to toggle the power on, do it. else if (power_on_bri) { power_t new_power; #ifdef USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) { active_remote_pwm_dimmer->power_on ^= 1; new_power = active_remote_pwm_dimmer->power_on; PWMDimmerSetBrightnessLeds(new_power ? -power_on_bri : 0); } else { #endif // USE_PWM_DIMMER_REMOTE new_power = TasmotaGlobal.power ^ 1; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE if (new_power) SendDeviceGroupMessage(power_button_index, DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_BRI, power_on_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 if (active_remote_pwm_dimmer) active_remote_pwm_dimmer->power_button_increases_bri = (power_on_bri < 128); else { #endif // USE_PWM_DIMMER_REMOTE light_state.setBri(power_on_bri); Settings.light_dimmer = light_state.BriToDimmer(power_on_bri); #ifdef USE_DEVICE_GROUPS Light.devgrp_no_channels_out = true; #endif // USE_DEVICE_GROUPS ExecuteCommandPower(1, POWER_TOGGLE, SRC_RETRY); #ifdef USE_DEVICE_GROUPS Light.devgrp_no_channels_out = false; #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE } // If the up or down button was tapped while holding the power button and the up or down button // is being held or was just released after not being held, handle the operation. if (handle_tap) { tap_handled = true; // If the down button was tapped while holding the power button, send a device group update to // select the previous/next fixed color. if (down_button_tapped) { #ifdef USE_DEVICE_GROUPS int8_t add_value = (is_down_button ? -1 : 1); #ifdef USE_PWM_DIMMER_REMOTE if (active_remote_pwm_dimmer) { active_remote_pwm_dimmer->fixed_color_index += add_value; dgr_value = active_remote_pwm_dimmer->fixed_color_index; } else { #endif // USE_PWM_DIMMER_REMOTE local_fixed_color_index += add_value; dgr_value = local_fixed_color_index; #ifdef USE_PWM_DIMMER_REMOTE } #endif // USE_PWM_DIMMER_REMOTE dgr_item = DGR_ITEM_LIGHT_FIXED_COLOR; #endif // USE_DEVICE_GROUPS ; } // If the up button was tapped while holding the power button, publish an MQTT Event Trigger#. else { mqtt_trigger = 3; if (is_down_button) mqtt_trigger = 4; } } // If we need to publish an MQTT trigger, do it. if (mqtt_trigger) { char topic[TOPSZ]; sprintf_P(TasmotaGlobal.mqtt_data, PSTR("Trigger%u"), mqtt_trigger); #ifdef USE_DEVICE_GROUPS if (Settings.flag4.device_groups_enabled) { snprintf_P(topic, sizeof(topic), PSTR("cmnd/%s/EVENT"), device_groups[power_button_index].group_name); MqttPublish(topic); } else #endif // USE_DEVICE_GROUPS MqttPublishPrefixTopic_P(CMND, PSTR("EVENT")); } // If we need to send a device group update, do it. if (dgr_item) { #ifdef USE_DEVICE_GROUPS DevGroupMessageType message_type = DGR_MSGTYP_UPDATE_DIRECT; #ifdef USE_PWM_DIMMER_REMOTE if (handle_tap && !active_remote_pwm_dimmer) #else // USE_PWM_DIMMER_REMOTE if (handle_tap) #endif // USE_PWM_DIMMER_REMOTE message_type = (DevGroupMessageType)(message_type + DGR_MSGTYPFLAG_WITH_LOCAL); SendDeviceGroupMessage(power_button_index, message_type, dgr_item, dgr_value); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_remote_pwm_dimmer) #endif // USE_PWM_DIMMER_REMOTE light_controller.saveSettings(); } if (state_updated && Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT #ifdef USE_PWM_DIMMER_REMOTE if (!active_remote_pwm_dimmer) #endif // USE_PWM_DIMMER_REMOTE MqttPublishTeleState(); } } /*********************************************************************************************\ * 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] > 1) parm[i]--; } else { value = strtoul(ptr, &ptr, 0); if (value < 1 || value > 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); } #ifdef USE_DEVICE_GROUPS void CmndPWMDimmerPWMs(void) { if (XdrvMailbox.data_len > 0 && XdrvMailbox.payload <= 5) { Settings.pwm_dimmer_cfg.pwm_count = XdrvMailbox.payload - 1; TasmotaGlobal.restart_flag = 2; } Response_P(PSTR("{\"" D_CMND_PWM_DIMMER_PWMS "\":%u}"), Settings.pwm_dimmer_cfg.pwm_count + 1); } #endif // USE_DEVICE_GROUPS /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv35(uint8_t function) { bool result = false; if (PWM_DIMMER != TasmotaGlobal.module_type) return result; switch (function) { case FUNC_EVERY_SECOND: // Turn off/restore the brightness LED's if it's time. if (led_timeout_seconds && !--led_timeout_seconds) { PWMDimmerSetBrightnessLeds(-2); } // 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 (TasmotaGlobal.global_state.data) restore_powered_off_led_counter = 5; else if (restore_powered_off_led_counter) { PWMDimmerSetPoweredOffLed(); restore_powered_off_led_counter--; } break; case FUNC_BUTTON_PRESSED: // If the button is pressed or was just released, ... if (!XdrvMailbox.payload || button_pressed[XdrvMailbox.index]) { uint32_t button_index = XdrvMailbox.index; uint32_t now = millis(); // If the button is pressed, ... if (!XdrvMailbox.payload) { // If the button was just pressed, flag the button as pressed, set the hold time and // increment the buttons pressed count. if (!button_pressed[button_index]) { button_pressed[button_index] = true; uint32_t hold_delay = 250; if (button_index == power_button_index) { #ifdef USE_PWM_DIMMER_REMOTE if (!(active_remote_pwm_dimmer ? active_remote_pwm_dimmer->power_on : TasmotaGlobal.power)) hold_delay = 500; #else // USE_PWM_DIMMER_REMOTE if (!TasmotaGlobal.power) hold_delay = 500; #endif // USE_PWM_DIMMER_REMOTE } button_hold_time[button_index] = now + hold_delay; buttons_pressed++; if (buttons_pressed > 1) multibutton_in_progress = true; #ifdef USE_PWM_DIMMER_REMOTE // 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. 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 and are based on the GPIOs // used on the MJ-SD01 type dimmers. // // Power Down Up // Position GPIO Button GPIO GPIO // -------- ---- ------ ---- ---- // Top 0 1 1 0 // Middle 1 2 15 0 // Bottom 15 3 15 1 if (buttons_pressed == 1 && Settings.flag4.multiple_device_groups) { power_button_index = button_index; down_button_index = (Pin(GPIO_KEY1, power_button_index) == 15 ? TasmotaGlobal.gpio_pin[1] : TasmotaGlobal.gpio_pin[15]) - 32; active_remote_pwm_dimmer = nullptr; if (power_button_index || !first_device_group_is_local) active_remote_pwm_dimmer = &remote_pwm_dimmers[power_button_index]; } #endif // USE_PWM_DIMMER_REMOTE } // If hold time has arrived and a rule is enabled that handles the button hold, handle it. else if (button_hold_time[button_index] <= now) { #ifdef USE_RULES sprintf(TasmotaGlobal.mqtt_data, PSTR("{\"Button%u\":{\"State\":3}}"), button_index + 1); Rules.no_execute = true; if (!XdrvRulesProcess()) { #endif // USE_RULES PWMDimmerHandleButton(button_index, true); button_held[button_index] = true; #ifdef USE_RULES } Rules.no_execute = false; #endif // USE_RULES } } // If the button was just released, flag the button as released and decrement the buttons // pressed count. else { button_pressed[button_index] = false; buttons_pressed--; // If this is a multibutton press or the button was held, handle it. if (multibutton_in_progress || button_held[button_index]) { PWMDimmerHandleButton(button_index, false); // Set a timer so FUNC_ANY_KEY ignores the button if support_button winds up sending a // key because of this. ignore_any_key_time = now + 500; // If a multi-button operation is in progress or the button was pressed, released and // then held, tell support_button that we've handled it. result = true; Button.press_counter[button_index] = 0; if (buttons_pressed == 0) multibutton_in_progress = false; button_unprocessed[button_index] = false; } else { button_unprocessed[button_index] = true; } // If the power button was just released, clear the flags associated with it. if (button_index == power_button_index) { if (ignore_power_button) ignore_any_key_time = now + 500; ignore_power_button = false; button_tapped = false; } button_held[button_index] = false; } } break; case FUNC_ANY_KEY: { uint32_t state = (XdrvMailbox.payload >> 8) & 0xFF; // 0 = Off, 1 = On, 2 = Toggle, 3 = Hold, 10,11,12,13 and 14 for Button Multipress if ((state == 2 || state == 10) && ignore_any_key_time < millis()) { uint32_t button_index = (XdrvMailbox.payload & 0xFF) - 1; button_unprocessed[button_index] = false; PWMDimmerHandleButton(button_index, false); } } break; #ifdef USE_DEVICE_GROUPS case FUNC_DEVICE_GROUP_ITEM: PWMDimmerHandleDevGroupItem(); break; #endif // USE_DEVICE_GROUPS case FUNC_COMMAND: result = DecodeCommand(kPWMDimmerCommands, PWMDimmerCommand); break; case FUNC_SET_DEVICE_POWER: // If we're turning the power on, turn the relay and the brightness LEDs on and turn the // powered-off LED off. if (XdrvMailbox.index) { PWMDimmerSetPower(); // Set the power button hold dimmer direction based on the current brightness. power_button_increases_bri = (light_state.getBri() < 128); } // If we're turning the power off, return true so SetDevicePower doesn't turn the relay off. // It will be turned off in LightApplyFade when the fade is done. else result = true; break; case FUNC_PRE_INIT: PWMModulePreInit(); break; } return result; } #endif // USE_PWM_DIMMER