From ba94c5df55c56deeae8b1b45b6a2c43397e08301 Mon Sep 17 00:00:00 2001 From: Till Date: Thu, 25 Jan 2024 15:06:11 +0100 Subject: [PATCH] Add long press to action button (#23) --- README.md | 7 +-- .../philips_action_button/action_button.cpp | 33 +++++++++++++ .../philips_action_button/action_button.h | 37 ++++++++++++++ components/philips_action_button/button.py | 29 ++++++++--- .../philips_series_2200.cpp | 15 +++++- .../philips_series_2200/philips_series_2200.h | 4 ++ .../philips_status_sensor/status_sensor.cpp | 49 +++++++++++++++++-- protocol.md | 26 +++++----- 8 files changed, 172 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index bc648d3..bdb624c 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,27 @@ A example configuration can be found [here](example.yaml) - **display_uart**(**Required**, string): ID of the UART-Component connected to the display unit - **mainboard_uart**(**Required**, string): ID of the UART-Component connected to the mainboard - **power_pin**(**Required**, [Pin](https://esphome.io/guides/configuration-types.html#config-pin)): Pin to which the MOSFET/Transistor is connected. This pin is used to temporarily turn of the display unit. -- **invert_power_pin**(**Optional**: boolean): If set to true the output of the power pin will be inverted. Defaults to `false`. +- **invert_power_pin**(**Optional**: boolean): If set to `true` the output of the power pin will be inverted. Defaults to `false`. - **power_trip_delay**(**Optional**: Time): Determines the length of the power outage applied to the display unit, which is to trick it into turning on. Defaults to `500ms`. ## Philips Power switch - **controller_id**(**Required**, string): The Philips Series 2200-Controller to which this entity belongs -- **clean**(**Optional**: boolean): If set to true the machine will perform a cleaning cycle during startup. Otherwise the machine will power on without cleaning. Defaults to `true`. +- **clean**(**Optional**: boolean): If set to `true` the machine will perform a cleaning cycle during startup. Otherwise the machine will power on without cleaning. Defaults to `true`. - All other options from [Switch](https://esphome.io/components/switch/index.html#config-switch) ## Philips Action Button - **controller_id**(**Required**, string): The Philips Series 2200-Controller to which this entity belongs - **action**(**Required**, int): The action performed by this button. Select one of `MAKE_COFFEE`, `SELECT_COFFEE`, `SELECT_ESPRESSO`, `MAKE_ESPRESSO`, `SELECT_HOT_WATER`, `MAKE_HOT_WATER`, `SELECT_STEAM`, `MAKE_STEAM`, `BEAN`, `SIZE`, `AQUA_CLEAN`, `CALC_CLEAN`, `PLAY_PAUSE`. +- **long_press**(**Optional**, boolean): If set to `true` this button will perform a long press. This option is only available for actions which don't include `MAKE`. - All other options from [Button](https://esphome.io/components/button/index.html#config-button) ## Philips Status Sensor - **controller_id**(**Required**, string): The Philips Series 2200-Controller to which this entity belongs - All other options from [Text Sensor](https://esphome.io/components/text_sensor/index.html#config-text-sensor) -- **use_cappuccino**(**Optional**, boolean): If set to true `Cappuccino selected` selected will be reported instead of `Steam selected`. This option is intended for machines like the EP2230 which can make cappuccino. Default to `false`. +- **use_cappuccino**(**Optional**, boolean): If set to `true`, `Cappuccino selected` will be reported instead of `Steam selected`. This option is intended for machines like the EP2230 which can make cappuccino. Default to `false`. ## Bean Settings diff --git a/components/philips_action_button/action_button.cpp b/components/philips_action_button/action_button.cpp index 8f301f1..b4971df 100644 --- a/components/philips_action_button/action_button.cpp +++ b/components/philips_action_button/action_button.cpp @@ -15,6 +15,24 @@ namespace esphome LOG_BUTTON("", "Philips Action Button", this); } + void ActionButton::loop() + { + // Repeated message sending for long presses + if (millis() - press_start_ <= LONG_PRESS_DURATION) + { + if (millis() - last_message_sent_ > LONG_PRESS_REPETITION_DELAY) + { + last_message_sent_ = millis(); + perform_action(); + } + is_long_pressing_ = true; + } + else + { + is_long_pressing_ = false; + } + } + void ActionButton::write_array(const std::vector &data) { for (unsigned int i = 0; i <= MESSAGE_REPETITIONS; i++) @@ -23,6 +41,21 @@ namespace esphome } void ActionButton::press_action() + { + if (should_long_press_) + { + // Reset button press start time + press_start_ = millis(); + last_message_sent_ = 0; + } + else + { + // Perform a single button press + perform_action(); + } + } + + void ActionButton::perform_action() { auto action = action_; // Coffee diff --git a/components/philips_action_button/action_button.h b/components/philips_action_button/action_button.h index 7df293c..7b497b7 100644 --- a/components/philips_action_button/action_button.h +++ b/components/philips_action_button/action_button.h @@ -6,6 +6,8 @@ #define MESSAGE_REPETITIONS 5 #define BUTTON_SEQUENCE_DELAY 100 +#define LONG_PRESS_REPETITION_DELAY 50 +#define LONG_PRESS_DURATION 3500 namespace esphome { @@ -43,6 +45,7 @@ namespace esphome { public: void dump_config() override; + void loop() override; /** * @brief Set the action used by this ActionButton. @@ -58,6 +61,26 @@ namespace esphome */ void set_uart_device(uart::UARTDevice *uart) { mainboard_uart_ = uart; }; + /** + * @brief Sets the long press parameter on this button component. + * + * @param long_press True if a long press should be executed, false otherwise + */ + void set_long_press(bool long_press) + { + should_long_press_ = long_press; + } + + /** + * @brief Determines if the button is currently performing a long press + * + * @return True if the button is currently performing a long press + */ + bool is_long_pressing() + { + return is_long_pressing_; + } + private: /** * @brief Writes data MESSAGE_REPETITIONS times to the mainboard uart @@ -72,10 +95,24 @@ namespace esphome */ void press_action() override; + /** + * @brief Writes the button to uart or initializes loop based message sending + * + */ + void perform_action(); + /// @brief Action used by this Button Action action_; /// @brief reference to uart connected to mainboard uart::UARTDevice *mainboard_uart_; + /// @brief time in ms for how long the button should be pressed. + bool should_long_press_ = false; + /// @brief true if the component is currently performing a long press + bool is_long_pressing_ = false; + /// @brief time at which the button press was started + long press_start_ = 0; + /// @brief time at which the last message was sent + long last_message_sent_ = 0; }; } // namespace philips_action_button } // namespace philips_series_2200 diff --git a/components/philips_action_button/button.py b/components/philips_action_button/button.py index 97f2e94..6018fe3 100644 --- a/components/philips_action_button/button.py +++ b/components/philips_action_button/button.py @@ -7,6 +7,7 @@ DEPENDENCIES = ["philips_series_2200"] CONF_ACTION = "action" +CONF_LONG_PRESS = "long_press" philips_action_button_ns = cg.esphome_ns.namespace("philips_series_2200").namespace( "philips_action_button" @@ -32,13 +33,26 @@ "PLAY_PAUSE": Action.PLAY_PAUSE, } -CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(ActionButton), - cv.Required(CONTROLLER_ID): cv.use_id(PhilipsSeries2200), - cv.Required(CONF_ACTION): cv.enum(ACTIONS, upper=True, space="_"), - } -).extend(cv.COMPONENT_SCHEMA) + +def validate_long_press(config): + """Validate that long press only applies to select options.""" + if config[CONF_LONG_PRESS] and "MAKE" in config[CONF_ACTION]: + raise cv.Invalid(f"Action {config[CONF_ACTION]} does not support long press.") + + return config + + +CONFIG_SCHEMA = cv.All( + button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ActionButton), + cv.Required(CONTROLLER_ID): cv.use_id(PhilipsSeries2200), + cv.Required(CONF_ACTION): cv.enum(ACTIONS, upper=True, space="_"), + cv.Optional(CONF_LONG_PRESS, default=False): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + validate_long_press, +) async def to_code(config): @@ -48,4 +62,5 @@ async def to_code(config): await button.register_button(var, config) cg.add(var.set_action(config[CONF_ACTION])) + cg.add(var.set_long_press(config[CONF_LONG_PRESS])) cg.add(parent.add_action_button(var)) diff --git a/components/philips_series_2200/philips_series_2200.cpp b/components/philips_series_2200/philips_series_2200.cpp index f78d04e..b20241c 100644 --- a/components/philips_series_2200/philips_series_2200.cpp +++ b/components/philips_series_2200/philips_series_2200.cpp @@ -27,7 +27,20 @@ namespace esphome uint8_t size = std::min(display_uart_.available(), BUFFER_SIZE); display_uart_.read_array(buffer, size); - mainboard_uart_.write_array(buffer, size); + // Check if a action button is currently performing a long press + bool long_pressing = false; + for (philips_action_button::ActionButton *button : action_buttons_) + { + if (button->is_long_pressing()) + { + long_pressing = true; + break; + } + } + + // Drop messages if button long-press is currently injecting messages + if (!long_pressing) + mainboard_uart_.write_array(buffer, size); last_message_from_display_time_ = millis(); } diff --git a/components/philips_series_2200/philips_series_2200.h b/components/philips_series_2200/philips_series_2200.h index 0031f37..ac9d468 100644 --- a/components/philips_series_2200/philips_series_2200.h +++ b/components/philips_series_2200/philips_series_2200.h @@ -79,6 +79,7 @@ namespace esphome void add_action_button(philips_action_button::ActionButton *action_button) { action_button->set_uart_device(&mainboard_uart_); + action_buttons_.push_back(action_button); } /** @@ -140,6 +141,9 @@ namespace esphome /// @brief list of registered water sensors std::vector size_setting_; + + /// @brief list of registered action buttons + std::vector action_buttons_; }; } // namespace philips_series_2200 diff --git a/components/philips_status_sensor/status_sensor.cpp b/components/philips_status_sensor/status_sensor.cpp index 873e785..6e51201 100644 --- a/components/philips_status_sensor/status_sensor.cpp +++ b/components/philips_status_sensor/status_sensor.cpp @@ -79,9 +79,24 @@ namespace esphome if (data[3] == 0x00 && data[4] == 0x00 && (data[5] == 0x07 || data[5] == 0x38) && data[6] == 0x00) { if (millis() - play_pause_last_change_ < BLINK_THRESHOLD) - update_state((data[5] == 0x07) ? "Coffee selected" : "2x Coffee selected"); + { + if (data[9] == 0x38) + { + update_state("Ground Coffee selected"); + } + else if (data[11] == 0x00) + { + update_state("Coffee programming mode selected"); + } + else + { + update_state((data[5] == 0x07) ? "Coffee selected" : "2x Coffee selected"); + } + } else + { update_state("Busy"); + } return; } @@ -89,7 +104,7 @@ namespace esphome if (data[3] == 0x00 && data[4] == 0x00 && data[5] == 0x00 && data[6] == 0x07) { if (millis() - play_pause_last_change_ < BLINK_THRESHOLD) - update_state(use_cappuccino_?"Cappuccino selected":"Steam selected"); + update_state(use_cappuccino_ ? "Cappuccino selected" : "Steam selected"); else update_state("Busy"); return; @@ -99,9 +114,20 @@ namespace esphome if (data[3] == 0x00 && data[4] == 0x07 && data[5] == 0x00 && data[6] == 0x00) { if (millis() - play_pause_last_change_ < BLINK_THRESHOLD) - update_state("Hot water selected"); + { + if (data[11] == 0x07) + { + update_state("Hot water selected"); + } + else + { + update_state("Hot water programming mode selected"); + } + } else + { update_state("Busy"); + } return; } @@ -109,9 +135,24 @@ namespace esphome if ((data[3] == 0x07 || data[3] == 0x38) && data[4] == 0x00 && data[5] == 0x00 && data[6] == 0x00) { if (millis() - play_pause_last_change_ < BLINK_THRESHOLD) - update_state((data[3] == 0x07) ? "Espresso selected" : "2x Espresso selected"); + { + if (data[9] == 0x38) + { + update_state("Ground Espresso selected"); + } + else if (data[11] == 0x00) + { + update_state("Espresso programming mode selected"); + } + else + { + update_state((data[3] == 0x07) ? "Espresso selected" : "2x Espresso selected"); + } + } else + { update_state("Busy"); + } return; } } diff --git a/protocol.md b/protocol.md index 2bd8128..deff3a6 100644 --- a/protocol.md +++ b/protocol.md @@ -97,19 +97,19 @@ The following table show the purpose of each byte and their known states | 0 | START | | 1 | START | | 2 | INSTRUCTION | -| 3 | Espresso-LED | `03`/`07` - half/full brightness ; `38` - 2x espresso | -| 4 | Hot Water-LED | `03`/`07` - half/full brightness | -| 5 | Coffee-LED | `03`/`07` - half/full brightness; `38` - 2x coffee | -| 6 | Steam-LED | `03`/`07` - half/full brightness | -| 7 | | unknown | -| 8 | Bean-LED | `00` - 1 LED; `38` - 2 LEDs; `3F` - 3 LEDs | -| 9 | Bean-LED | `07` - show led group; `38` - powder selected | -| 10 | Size-LED | `00` - 1 LED; `38` - 2 LEDs; `3F` - 3 LEDs | -| 11 | Size-LED | `07` - show led group | -| 12 | | probably aqua_clean/calc_clean | -| 13 | | probably aqua_clean/calc_clean | -| 14 | Waste&Warning-LED | `07` - waste; `38` - warning sign | -| 15 | Play/Pause-LED | `07` - on | +| 3 | Espresso-LED | `03`/`07` - half/full brightness ; `38` - 2x espresso | +| 4 | Hot Water-LED | `03`/`07` - half/full brightness | +| 5 | Coffee-LED | `03`/`07` - half/full brightness; `38` - 2x coffee | +| 6 | Steam-LED | `03`/`07` - half/full brightness | +| 7 | | unknown | +| 8 | Bean-LED | `00` - 1 LED; `38` - 2 LEDs; `3F` - 3 LEDs | +| 9 | Bean-LED | `07` - show led group; `38` - powder selected | +| 10 | Size-LED | `00` - 1 LED; `38` - 2 LEDs; `3F` - 3 LEDs; `07` - Top LED | +| 11 | Size-LED | `07` - show led group | +| 12 | | probably aqua_clean/calc_clean | +| 13 | | probably aqua_clean/calc_clean | +| 14 | Waste&Warning-LED | `07` - waste; `38` - warning sign | +| 15 | Play/Pause-LED | `07` - on | | 16 | checksum | | 17 | checksum |