diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 8a490787e..204818035 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -465,8 +465,8 @@ #define DEVICE_GROUPS_ADDRESS 239,255,250,250 // Device groups multicast address #define DEVICE_GROUPS_PORT 4447 // Device groups multicast port #define USE_DEVICE_GROUPS_SEND // Add support for the DevGroupSend command (+0k6 code) -#define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+2k2 code, DGR=0k4) - #define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer (requires USE_DEVICE_GROUPS) (+0k9 code) +#define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+2k3 code, DGR=0k7) + #define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer (requires USE_DEVICE_GROUPS) (+0k6 code) //#define USE_KEELOQ // Add support for Jarolift rollers by Keeloq algorithm (+4k5 code) #define USE_SONOFF_D1 // Add support for Sonoff D1 Dimmer (+0k7 code) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index a3df56877..9176f3a7c 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1885,7 +1885,13 @@ void CmndLedState(void) { void CmndLedMask(void) { if (XdrvMailbox.data_len > 0) { +#ifdef USE_PWM_DIMMER + PWMDimmerSetBrightnessLeds(0); +#endif // USE_PWM_DIMMER Settings.ledmask = XdrvMailbox.payload; +#ifdef USE_PWM_DIMMER + PWMDimmerSetBrightnessLeds(-1); +#endif // USE_PWM_DIMMER } char stemp1[TOPSZ]; snprintf_P(stemp1, sizeof(stemp1), PSTR("%d (0x%04X)"), Settings.ledmask, Settings.ledmask); diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 510ba53f2..7fb719d0f 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -48,7 +48,6 @@ struct device_group { uint16_t ack_check_interval; uint8_t message_header_length; uint8_t initial_status_requests_remaining; - bool local; char group_name[TOPSZ]; uint8_t message[128]; struct device_group_member * device_group_members; @@ -157,8 +156,6 @@ void DeviceGroupsInit(void) device_group->last_full_status_sequence = -1; } - device_groups[0].local = true; - // If both in and out shared items masks are 0, assume they're unitialized and initialize them. if (!Settings.device_group_share_in && !Settings.device_group_share_out) { Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff; @@ -296,6 +293,7 @@ void SendReceiveDeviceGroupMessage(struct device_group * device_group, struct de */ XdrvMailbox.command = nullptr; // Indicates the source is a device group update XdrvMailbox.index = flags | message_sequence << 16; + if (device_group_index == 0 && first_device_group_is_local) XdrvMailbox.index |= DGR_FLAG_LOCAL; XdrvMailbox.topic = (char *)&device_group_index; if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) skip_light_fade = true; @@ -388,10 +386,12 @@ void SendReceiveDeviceGroupMessage(struct device_group * device_group, struct de switch (item) { case DGR_ITEM_POWER: if (Settings.flag4.multiple_device_groups) { // SetOption88 - Enable relays in separate device groups - bool on = (value & 1); - if (on != (power & (1 << device_group_index))) ExecuteCommandPower(device_group_index + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE); + if (device_group_index < devices_present) { + bool on = (value & 1); + if (on != (power & (1 << device_group_index))) ExecuteCommandPower(device_group_index + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE); + } } - else if (device_group->local) { + else if (XdrvMailbox.index & DGR_FLAG_LOCAL) { uint8_t mask_devices = value >> 24; if (mask_devices > devices_present) mask_devices = devices_present; for (uint32_t i = 0; i < mask_devices; i++) { @@ -505,10 +505,9 @@ bool _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes building_status_message = true; // Call the drivers to build the status update. - if (device_group->local || Settings.flag4.multiple_device_groups) { // SetOption88 - Enable relays in separate device groups - SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); - } - XdrvMailbox.index = device_group_index << 16; + SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); + XdrvMailbox.index = 0; + if (device_group_index == 0 && first_device_group_is_local) XdrvMailbox.index = DGR_FLAG_LOCAL; XdrvMailbox.command_code = DGR_ITEM_STATUS; XdrvMailbox.topic = (char *)&device_group_index; XdrvCall(FUNC_DEVICE_GROUP_ITEM); @@ -680,7 +679,7 @@ bool _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes *message_ptr++ = value & 0xff; value >>= 8; // For the power item, the device count is overlayed onto the highest 8 bits. - if (item == DGR_ITEM_POWER && !value) value = (device_group_index == 0 ? devices_present : 1); + if (item == DGR_ITEM_POWER && !value) value = (device_group_index == 0 && first_device_group_is_local ? devices_present : 1); *message_ptr++ = value; } } diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 0549e76ec..da342868a 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -492,6 +492,9 @@ bool SendKey(uint32_t key, uint32_t device, uint32_t state) XdrvMailbox.payload = device_save << 24 | key << 16 | state << 8 | device; XdrvCall(FUNC_ANY_KEY); XdrvMailbox.payload = payload_save; +#ifdef USE_PWM_DIMMER + if (PWM_DIMMER == my_module_type) result = true; +#endif // USE_PWM_DIMMER return result; } diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index fd66c0d05..6a5c4f851 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -310,7 +310,7 @@ enum SettingsTextIndex { SET_OTAURL, enum DevGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYPE_UPDATE_COMMAND, DGR_MSGTYPFLAG_WITH_LOCAL = 128 }; -enum DevGroupMessageFlag { DGR_FLAG_RESET = 1, DGR_FLAG_STATUS_REQUEST = 2, DGR_FLAG_FULL_STATUS = 4, DGR_FLAG_ACK = 8, DGR_FLAG_MORE_TO_COME = 16, DGR_FLAG_DIRECT = 32, DGR_FLAG_ANNOUNCEMENT = 64 }; +enum DevGroupMessageFlag { DGR_FLAG_RESET = 1, DGR_FLAG_STATUS_REQUEST = 2, DGR_FLAG_FULL_STATUS = 4, DGR_FLAG_ACK = 8, DGR_FLAG_MORE_TO_COME = 16, DGR_FLAG_DIRECT = 32, DGR_FLAG_ANNOUNCEMENT = 64, DGR_FLAG_LOCAL = 128 }; enum DevGroupItem { DGR_ITEM_EOL, DGR_ITEM_STATUS, DGR_ITEM_FLAGS, DGR_ITEM_LIGHT_FADE, DGR_ITEM_LIGHT_SPEED, DGR_ITEM_LIGHT_BRI, DGR_ITEM_LIGHT_SCHEME, DGR_ITEM_LIGHT_FIXED_COLOR, diff --git a/tasmota/tasmota_globals.h b/tasmota/tasmota_globals.h index b5343af80..e245f3f2f 100644 --- a/tasmota/tasmota_globals.h +++ b/tasmota/tasmota_globals.h @@ -391,6 +391,7 @@ const char kWebColors[] PROGMEM = #define SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, ...) _SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, __VA_ARGS__, 0) #define SendLocalDeviceGroupMessage(REQUEST_TYPE, ...) _SendDeviceGroupMessage(0, REQUEST_TYPE, __VA_ARGS__, 0) uint8_t device_group_count = 0; +bool first_device_group_is_local = true; #endif // USE_DEVICE_GROUPS #ifdef DEBUG_TASMOTA_CORE diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index f6f3ced85..6a507db06 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -2336,11 +2336,12 @@ void LightHandleDevGroupItem(void) { static bool send_state = false; static bool restore_power = false; + +#ifdef USE_PWM_DIMMER_REMOTE + if (!(XdrvMailbox.index & DGR_FLAG_LOCAL)) return; +#endif // USE_PWM_DIMMER_REMOTE bool more_to_come; uint32_t value = XdrvMailbox.payload; -#ifdef USE_PWM_DIMMER_REMOTE - if (*XdrvMailbox.topic) return; // Ignore updates from other device groups -#endif // USE_PWM_DIMMER_REMOTE switch (XdrvMailbox.command_code) { case DGR_ITEM_EOL: more_to_come = (XdrvMailbox.index & DGR_FLAG_MORE_TO_COME); diff --git a/tasmota/xdrv_35_pwm_dimmer.ino b/tasmota/xdrv_35_pwm_dimmer.ino index 359fa6a54..67d7e2149 100644 --- a/tasmota/xdrv_35_pwm_dimmer.ino +++ b/tasmota/xdrv_35_pwm_dimmer.ino @@ -31,6 +31,7 @@ \*********************************************************************************************/ #define XDRV_35 35 +#define MAX_PWM_DIMMER_KEYS 3 const char kPWMDimmerCommands[] PROGMEM = "|" // No prefix D_CMND_BRI_PRESET @@ -58,26 +59,26 @@ struct remote_pwm_dimmer { }; #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 ignore_power_button = false; bool button_tapped = false; bool down_button_tapped = false; -bool tap_handled = 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_hold_sent[3]; -bool button_hold_processed[3]; +bool button_held[3]; #ifdef USE_PWM_DIMMER_REMOTE -struct remote_pwm_dimmer * remote_pwm_dimmers; +struct remote_pwm_dimmer remote_pwm_dimmers[MAX_PWM_DIMMER_KEYS]; struct remote_pwm_dimmer * active_remote_pwm_dimmer; -uint8_t remote_pwm_dimmer_count; -bool active_device_is_local; #endif // USE_PWM_DIMMER_REMOTE void PWMModulePreInit(void) @@ -89,9 +90,14 @@ void PWMModulePreInit(void) // If the module was just changed to PWM Dimmer, set the defaults. if (Settings.last_module != Settings.module) { Settings.flag.pwm_control = true; // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL - Settings.param[P_HOLD_TIME] = 5; // SetOption32 - Button held for factor times longer Settings.bri_power_on = Settings.bri_preset_low = Settings.bri_preset_high = 0; Settings.last_module = Settings.module; + + // 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. @@ -111,54 +117,65 @@ void PWMModulePreInit(void) Settings.flag4.device_groups_enabled = true; device_group_count = 0; - for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { + for (uint32_t button_index = 0; button_index < MAX_PWM_DIMMER_KEYS; button_index++) { if (PinUsed(GPIO_KEY1, button_index)) device_group_count++; } - remote_pwm_dimmer_count = device_group_count - 1; - if (remote_pwm_dimmer_count) { - if ((remote_pwm_dimmers = (struct remote_pwm_dimmer *) calloc(remote_pwm_dimmer_count, sizeof(struct remote_pwm_dimmer))) == nullptr) { - AddLog_P2(LOG_LEVEL_ERROR, PSTR("PWMDimmer: error allocating PWM dimmer array")); - Settings.flag4.multiple_device_groups = false; - } - else { - for (uint8_t i = 0; i < remote_pwm_dimmer_count; 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; - } - } + // 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. + devices_present--; + 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; } } - active_device_is_local = true; #endif // USE_PWM_DIMMER_REMOTE } // bri: -1 = set to current light bri, -2 = timeout, 0-255 = set to bri void PWMDimmerSetBrightnessLeds(int32_t bri) { - if (leds_present) { + // 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 < 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_present + 1); - uint32_t level = step; - SetLedPowerIdx(0, bri >= level); - if (leds_present > 1) { + 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; - SetLedPowerIdx(1, bri >= level); - if (leds_present > 2) { - level += step; - SetLedPowerIdx(2, bri >= level); - if (leds_present > 3) { - level += step; - SetLedPowerIdx(3, bri >= level); - } + for (;;) { + led++; + mask <<= 1; + if (!mask) mask = 1; + if (Settings.ledmask & mask) break; } + SetLedPowerIdx(led, bri >= level); } } } @@ -183,105 +200,67 @@ void PWMDimmerSetPower(void) #ifdef USE_DEVICE_GROUPS void PWMDimmerHandleDevGroupItem(void) { - uint32_t value = XdrvMailbox.payload; #ifdef USE_PWM_DIMMER_REMOTE uint8_t device_group_index = *(uint8_t *)XdrvMailbox.topic; - if (device_group_index > remote_pwm_dimmer_count) return; - bool device_is_local = device_groups[device_group_index].local; - struct remote_pwm_dimmer * remote_pwm_dimmer = &remote_pwm_dimmers[device_group_index - 1]; + 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 (*(uint8_t *)XdrvMailbox.topic) return; + 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: - if (!device_is_local) remote_pwm_dimmer->bri = value; + remote_pwm_dimmer->bri = value; break; case DGR_ITEM_POWER: - if (!device_is_local) { - remote_pwm_dimmer->power_on = value & 1; - remote_pwm_dimmer->power_button_increases_bri = (remote_pwm_dimmer->bri < 128); - } + 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 - if (!device_is_local) - remote_pwm_dimmer->fixed_color_index = value; - else + 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 - if (!device_is_local) - remote_pwm_dimmer->bri_power_on = value; - else + 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 - if (!device_is_local) - remote_pwm_dimmer->bri_preset_low = value; - else + 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 - if (!device_is_local) - remote_pwm_dimmer->bri_preset_high = value; - else + 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 (device_is_local) + 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); + DGR_ITEM_BRI_PRESET_LOW, Settings.bri_preset_low, DGR_ITEM_BRI_PRESET_HIGH, Settings.bri_preset_high); break; } } #endif // USE_DEVICE_GROUPS -void PWMDimmerHandleButton(void) +void PWMDimmerHandleButton(uint32_t button_index, bool pressed) { - /* - * 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 Hold up On No Brigther - * Released Hold 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 - */ - - // If the button is not pressed and was not just released (the most common case), just return. - if (XdrvMailbox.payload && !button_pressed[XdrvMailbox.index]) return; - bool handle_tap = false; bool state_updated = false; int32_t bri_offset = 0; @@ -290,260 +269,208 @@ void PWMDimmerHandleButton(void) uint8_t dgr_value = 0; uint8_t dgr_more_to_come = true; uint8_t mqtt_trigger = 0; - uint32_t button_index = XdrvMailbox.index; - uint32_t now = millis(); // Initialize some variables. #ifdef USE_PWM_DIMMER_REMOTE - bool power_is_on = (!active_device_is_local ? active_remote_pwm_dimmer->power_on : power); + bool power_is_on = (active_remote_pwm_dimmer ? active_remote_pwm_dimmer->power_on : 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 = power; bool is_power_button = !button_index; -#endif // USE_PWM_DIMMER_REMOTE bool is_down_button = (button_index == (power_button_index ? 0 : 1)); - - // If the button is pressed, ... - if (!XdrvMailbox.payload) { - - // If the button was just pressed, flag the button as pressed, clear the hold sent flag and - // increment the buttons pressed count. - if (!button_pressed[button_index]) { - button_pressed[button_index] = true; - button_hold_time[button_index] = now + Settings.param[P_HOLD_TIME] * 100; - buttons_pressed++; - -#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. - if (buttons_pressed == 1 && Settings.flag4.multiple_device_groups) { - power_button_index = button_index; - 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]; - } #endif // USE_PWM_DIMMER_REMOTE - return; + // If the button is being held, ... + if (pressed) { + uint32_t now = millis(); + + // If this is about the power button, ... + 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 the button is being held, ... - if (button_hold_time[button_index] < now) { + // If this is about the down or up buttons, ... + else { - // If we're not in the middle of a power button plus up/down button sequence and the button - // has been held for over 10 seconds, execute the WiFiConfig 2 command. - if (!ignore_power_button && now - button_hold_time[button_index] > 10000) { - button_hold_time[button_index] = now + 90000; - char scmnd[20]; - snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " 2")); - ExecuteCommand(scmnd, SRC_BUTTON); - return; - } + // If the power button is also pressed, ... + if (button_pressed[power_button_index]) { - // Send a button hold if we haven't already. If it is handled (by the button topic or by a - // rule), ignore the this button until it's released. - if (!button_hold_sent[button_index]) { - button_hold_sent[button_index] = true; - button_hold_processed[button_index] = (!is_power_button && button_tapped ? false : SendKey(KEY_BUTTON, button_index + 1, POWER_HOLD)); - } - if (!button_hold_processed[button_index]) { - - // If this is about the power button, ... - 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_device_is_local ? (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_device_is_local) - 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 the up or down button was tapped while holding the power button before this, handle + // the operation below. + if (button_tapped) { + handle_tap = true; + button_hold_time[button_index] = now + 500; } - // If this is about the down or up buttons, ... + // 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 { - - // 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 = true; - button_hold_time[button_index] = now + 500; - } - - // 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); - } + 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_hold_sent[button_index]; - button_hold_sent[button_index] = false; + bool button_was_held = button_held[button_index]; - // If the button was not held, send a button toggle. If the button was held but not processes by - // support_button or support_buttondoesn't process the toggle (is not handled by a rule), ... - if (!(button_was_held ? button_hold_processed[button_index] : SendKey(KEY_BUTTON, button_index + 1, POWER_TOGGLE))) { + // If this is about the power button, ... + if (is_power_button) { - // 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) { + // 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_device_is_local) { + 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 + // 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; } + tap_handled = false; + } - // Otherwise, if the power button was held, ... - else if (button_was_held) { + // 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; + // 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_device_is_local) - 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_device_is_local) - power_on_bri = active_remote_pwm_dimmer->bri_power_on; + if (active_remote_pwm_dimmer) + active_remote_pwm_dimmer->power_button_increases_bri ^= 1; else #endif // USE_PWM_DIMMER_REMOTE - power_on_bri = Settings.bri_power_on; + power_button_increases_bri ^= 1; +#ifdef USE_PWM_DIMMER_REMOTE + dgr_item = DGR_ITEM_FLAGS; + state_updated = true; +#endif // USE_PWM_DIMMER_REMOTE } } - // 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 ? -1 : 1); - dgr_more_to_come = false; - state_updated = true; - } - - // If the button was held and the hold was not processed by a rule, we changed the - // brightness and sent updates with the more-to-come message type while the button was - // held. Send a final update. - else if (!button_hold_processed[button_index]) { - 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 { + // 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_device_is_local) - 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 + if (active_remote_pwm_dimmer) + power_on_bri = active_remote_pwm_dimmer->bri_power_on; + else #endif // USE_PWM_DIMMER_REMOTE - power_on_bri = (is_down_button ? Settings.bri_preset_low : Settings.bri_preset_high); - } - } + power_on_bri = Settings.bri_power_on; } } - // Flag the button as released. - button_pressed[button_index] = false; - buttons_pressed--; - if (is_power_button) { - ignore_power_button = false; - button_tapped = false; + // 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); + } + } } } @@ -551,7 +478,7 @@ void PWMDimmerHandleButton(void) if (bri_offset) { int32_t bri; #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) + if (active_remote_pwm_dimmer) bri = active_remote_pwm_dimmer->bri; else #endif // USE_PWM_DIMMER_REMOTE @@ -569,7 +496,7 @@ void PWMDimmerHandleButton(void) SendDeviceGroupMessage(power_button_index, (dgr_more_to_come ? DGR_MSGTYP_UPDATE_MORE_TO_COME : DGR_MSGTYP_UPDATE), DGR_ITEM_LIGHT_BRI, new_bri); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) { + if (active_remote_pwm_dimmer) { active_remote_pwm_dimmer->bri_power_on = active_remote_pwm_dimmer->bri = new_bri; PWMDimmerSetBrightnessLeds(new_bri); } @@ -591,7 +518,7 @@ void PWMDimmerHandleButton(void) #endif // USE_PWM_DIMMER_REMOTE } #ifdef USE_PWM_DIMMER_REMOTE - else if (!active_device_is_local) + else if (active_remote_pwm_dimmer) PWMDimmerSetBrightnessLeds(new_bri); #endif // USE_PWM_DIMMER_REMOTE else @@ -603,7 +530,7 @@ void PWMDimmerHandleButton(void) power_t new_power; #ifdef USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) { + 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); @@ -621,7 +548,7 @@ void PWMDimmerHandleButton(void) #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) + if (active_remote_pwm_dimmer) active_remote_pwm_dimmer->power_button_increases_bri = (power_on_bri < 128); else { #endif // USE_PWM_DIMMER_REMOTE @@ -649,7 +576,7 @@ void PWMDimmerHandleButton(void) #ifdef USE_DEVICE_GROUPS int8_t add_value = (is_down_button ? -1 : 1); #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) { + if (active_remote_pwm_dimmer) { active_remote_pwm_dimmer->fixed_color_index += add_value; dgr_value = active_remote_pwm_dimmer->fixed_color_index; } @@ -675,35 +602,39 @@ void PWMDimmerHandleButton(void) char topic[TOPSZ]; sprintf_P(mqtt_data, PSTR("Trigger%u"), mqtt_trigger); #ifdef USE_PWM_DIMMER_REMOTE - if (!active_device_is_local) { - snprintf_P(topic, sizeof(topic), PSTR("cmnd/%s/Event"), device_groups[power_button_index].group_name); + if (active_remote_pwm_dimmer) { + snprintf_P(topic, sizeof(topic), PSTR("cmnd/%s/EVENT"), device_groups[power_button_index].group_name); MqttPublish(topic); } else #endif // USE_PWM_DIMMER_REMOTE - MqttPublishPrefixTopic_P(CMND, PSTR("Event")); + MqttPublishPrefixTopic_P(CMND, PSTR("EVENT")); } - // If we're not changing the brightness or toggling the power and we made changes, send a group - // update. + // 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; - if (handle_tap && active_device_is_local) message_type = (DevGroupMessageType)(message_type + DGR_MSGTYPFLAG_WITH_LOCAL); +#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_device_is_local) + if (!active_remote_pwm_dimmer) #endif // USE_PWM_DIMMER_REMOTE light_controller.saveSettings(); } - if (state_updated) + 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_device_is_local) + if (!active_remote_pwm_dimmer) #endif // USE_PWM_DIMMER_REMOTE - if (Settings.flag3.hass_tele_on_power) // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT - MqttPublishTeleState(); + MqttPublishTeleState(); + } } /*********************************************************************************************\ @@ -796,8 +727,92 @@ bool Xdrv35(uint8_t function) break; case FUNC_BUTTON_PRESSED: - PWMDimmerHandleButton(); - result = true; + // 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; + button_hold_time[button_index] = now + (button_index == power_button_index ? 500 : 250); + 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 ? gpio_pin[1] : 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, handle it. + else if (button_hold_time[button_index] <= now) { + PWMDimmerHandleButton(button_index, true); + button_held[button_index] = true; + } + } + + // 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 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; + } + + // If a multi-button operation is in progress, tell support_button that we've handled it. + if (multibutton_in_progress) { + result = true; + if (buttons_pressed == 0) multibutton_in_progress = 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()) { + PWMDimmerHandleButton((XdrvMailbox.payload & 0xFF) - 1, false); + } + } break; #ifdef USE_DEVICE_GROUPS