From d15916fbdce94160d9980958fbd4140b3799b09f Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Wed, 26 May 2021 17:50:45 +1200 Subject: [PATCH 01/10] Make this usable as an external_component Since v1.18.0 esphome supports external components which allow you to use custom components much more easily. https://esphome.io/components/external_components.html --- .gitignore | 3 ++ components/mitsubishi_heatpump/__init__.py | 46 +++++++++++++++++++ .../mitsubishi_heatpump/espmhp.cpp | 0 .../mitsubishi_heatpump/espmhp.h | 0 4 files changed, 49 insertions(+) create mode 100644 components/mitsubishi_heatpump/__init__.py rename espmhp.cpp => components/mitsubishi_heatpump/espmhp.cpp (100%) rename espmhp.h => components/mitsubishi_heatpump/espmhp.h (100%) diff --git a/.gitignore b/.gitignore index 259148f..9c89171 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ *.exe *.out *.app + +# Python cache +__pycache__ diff --git a/components/mitsubishi_heatpump/__init__.py b/components/mitsubishi_heatpump/__init__.py new file mode 100644 index 0000000..b5f213d --- /dev/null +++ b/components/mitsubishi_heatpump/__init__.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.components.logger import HARDWARE_UART_TO_SERIAL +from esphome.const import CONF_ID, CONF_HARDWARE_UART, CONF_UPDATE_INTERVAL +from esphome.core import CORE, coroutine + +AUTO_LOAD = ["climate"] + +MitsubishiHeatPump = cg.global_ns.class_("MitsubishiHeatPump", climate.Climate, cg.PollingComponent) + + +def valid_uart(uart): + if CORE.is_esp8266: + uarts = ["UART0"] # UART1 is tx-only + elif CORE.is_esp32: + uarts = ["UART0", "UART1", "UART2"] + else: + raise NotImplementedError + + return cv.one_of(*uarts, upper=True)(uart) + + +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MitsubishiHeatPump), + cv.Optional(CONF_HARDWARE_UART, default="UART0"): valid_uart, + + # If polling interval is greater than 9 seconds, the HeatPump library + # reconnects, but doesn't then follow up with our data request. + cv.Optional(CONF_UPDATE_INTERVAL, default="500ms"): cv.All( + cv.update_interval, + cv.Range(max=cv.TimePeriod(milliseconds=9000)) + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +@coroutine +def to_code(config): + serial = HARDWARE_UART_TO_SERIAL[config[CONF_HARDWARE_UART]] + var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f'&{serial}')) + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + cg.add_library("https://github.com/SwiCago/HeatPump", None) + diff --git a/espmhp.cpp b/components/mitsubishi_heatpump/espmhp.cpp similarity index 100% rename from espmhp.cpp rename to components/mitsubishi_heatpump/espmhp.cpp diff --git a/espmhp.h b/components/mitsubishi_heatpump/espmhp.h similarity index 100% rename from espmhp.h rename to components/mitsubishi_heatpump/espmhp.h From 1aa42c9406daec3e9b3a8a0c226ab66125598ebc Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Wed, 26 May 2021 17:50:57 +1200 Subject: [PATCH 02/10] Update documentation to use external_components --- README.md | 101 +++++++++++++++++++++++------------------------------- 1 file changed, 43 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index bcd002a..1605c50 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ESP32 using the [ESPHome](https://esphome.io) framework. ## Requirements * https://github.com/SwiCago/HeatPump -* ESPHome 1.15.0 or greater +* ESPHome 1.18.0 or greater ## Supported Microcontrollers This library should work on most ESP8266 or ESP32 platforms. It has been tested @@ -50,53 +50,46 @@ to the control board](https://github.com/SwiCago/HeatPump/issues/13#issuecomment-457897457) via CN105. -### Step 2: Use ESPHome 1.15.0 or higher +### Step 2: Use ESPHome 1.18.0 or higher -The code in this repository makes use of a number of features in the 1.15.0 version of ESPHome, including various Fan modes. +The code in this repository makes use of a number of features in the 1.18.0 +version of ESPHome, including various Fan modes and external components. -### Step 3: Clone this repository into your ESPHome configuration directory +### Step 3: Add this repository as an external component -This repository needs to live in your ESPHome configuration directory, as it -doesn't work correctly when used as a Platform.IO library, and there doesn't -seem to be an analog for that functionality for ESPHome code. - -On Hass.IO, you'll want to do something like: - -* Change directories to your esphome configuration directory. -* `mkdir -p src` -* `cd src` -* `git clone https://github.com/geoffdavis/esphome-mitsubishiheatpump.git` - -### Step 4: Configure your ESPHome device with YAML - -Create an ESPHome YAML configuration with the following sections: - * `esphome: libraries: [https://github.com/SwiCago/HeatPump]` - * `esphome: includes: [src/esphome-mitsubishiheatpump]` - * `climate:` - set up a custom climate entry, change the Serial port as needed. - * ESP8266 only: `logger: baud_rate: 0` - disable serial port logging on the - sole ESP8266 hardware UART - -The custom climate definition should use `platform: custom` and contain a -`lambda` block, where you instanciate an instance of the MitsubishiHeatPump -class, and then register it with ESPHome. It should allso contain a "climates" -entry. On ESP32 you -can change `&Serial` to `&Serial1` or `&Serial2` and re-enable logging to the -main serial port. - -If that's all greek to you, here's an example. Change "My Heat Pump" to -whatever you want. +Add this repository to your ESPHome config: ```yaml -climate: - - platform: custom - lambda: |- - auto my_heatpump = new MitsubishiHeatPump(&Serial); - App.register_component(my_heatpump); - return {my_heatpump}; - climates: - - name: "My Heat Pump" +external_components: + - source: github://geoffdavis/esphome-mitsubishiheatpump ``` +### Step 4: Configure the heatpump + +Add a `mitsubishi_heatpump` to your ESPHome config: + +```yaml +mitsubishi_heatpump: + name: "My Heat Pump" + + # Optional + hardware_uart: UART0 + + # Optional + update_period: 500ms +``` + +On ESP8266 you'll need to disable logging to serial because it conflicts with +the heatpump UART: + +```yaml +logger: + baud_rate: 0 +``` + +On ESP32 you can change `hardware_uart` to `UART1` or `UART2` and keep logging +enabled on the main serial port. + Note: this component DOES NOT use the ESPHome `uart` component, as it requires direct access to a hardware UART via the Arduino `HardwareSerial` class. The Mitsubishi Heatpump units use an atypical serial port setting ("even parity"). @@ -119,12 +112,6 @@ esphome: board: esp01_1m # Boards tested: ESP-01S (ESP8266), Wemos D1 Mini (ESP8266); ESP32 Wifi-DevKit2 - libraries: - - https://github.com/SwiCago/HeatPump - - includes: - - src/esphome-mitsubishiheatpump - wifi: ssid: !secret wifi_ssid password: !secret wifi_password @@ -181,18 +168,16 @@ sensor: name: denheatpump WiFi Signal update_interval: 60s +external_components: + - source: github://geoffdavis/esphome-mitsubishiheatpump -climate: - - platform: custom - # ESP32 only - change &Serial to &Serial1 or &Serial2 and remove the - # logging:baud_rate above to allow the built-in UART0 to function for - # logging. - lambda: |- - auto my_heatpump = new MitsubishiHeatPump(&Serial); - App.register_component(my_heatpump); - return {my_heatpump}; - climates: - - name: "Den Heat Pump" +mitsubishi_heatpump: + name: "Den Heat Pump" + + # ESP32 only - change UART0 to UART1 or UART2 and remove the + # logging:baud_rate above to allow the built-in UART0 to function for + # logging. + hardware_uart: UART0 ``` # See Also From 65edd60078710959e5d67de2764107e75c47a26c Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Wed, 26 May 2021 20:56:57 +1200 Subject: [PATCH 03/10] Configure the ClimateTraits in YAML Some heatpump models (don't) support different traits so it's useful to be able to override these directly in the config file. --- components/mitsubishi_heatpump/__init__.py | 39 ++++++++++++++++- components/mitsubishi_heatpump/espmhp.cpp | 50 +++++++++------------- components/mitsubishi_heatpump/espmhp.h | 6 +++ 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/components/mitsubishi_heatpump/__init__.py b/components/mitsubishi_heatpump/__init__.py index b5f213d..c134dea 100644 --- a/components/mitsubishi_heatpump/__init__.py +++ b/components/mitsubishi_heatpump/__init__.py @@ -2,11 +2,23 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate from esphome.components.logger import HARDWARE_UART_TO_SERIAL -from esphome.const import CONF_ID, CONF_HARDWARE_UART, CONF_UPDATE_INTERVAL +from esphome.const import ( + CONF_ID, + CONF_HARDWARE_UART, + CONF_UPDATE_INTERVAL, + CONF_MODE, + CONF_FAN_MODE, + CONF_SWING_MODE, +) from esphome.core import CORE, coroutine AUTO_LOAD = ["climate"] +CONF_SUPPORTS = "supports" +DEFAULT_CLIMATE_MODES = ['AUTO', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY'] +DEFAULT_FAN_MODES = ['AUTO', 'DIFFUSE', 'LOW', 'MEDIUM', 'MIDDLE', 'HIGH'] +DEFAULT_SWING_MODES = ['OFF', 'VERTICAL'] + MitsubishiHeatPump = cg.global_ns.class_("MitsubishiHeatPump", climate.Climate, cg.PollingComponent) @@ -32,6 +44,18 @@ CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( cv.update_interval, cv.Range(max=cv.TimePeriod(milliseconds=9000)) ), + + # Optionally override the supported ClimateTraits. + cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( + { + cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): + cv.ensure_list(climate.validate_climate_mode), + cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): + cv.ensure_list(climate.validate_climate_fan_mode), + cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): + cv.ensure_list(climate.validate_climate_swing_mode), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -40,6 +64,19 @@ CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( def to_code(config): serial = HARDWARE_UART_TO_SERIAL[config[CONF_HARDWARE_UART]] var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f'&{serial}')) + + traits = [] + for mode in config[CONF_SUPPORTS][CONF_MODE]: + if mode == 'OFF': + continue + traits.append(f'set_supports_{mode.lower()}_mode') + for mode in config[CONF_SUPPORTS][CONF_FAN_MODE]: + traits.append(f'set_supports_fan_mode_{mode.lower()}') + for mode in config[CONF_SUPPORTS][CONF_SWING_MODE]: + traits.append(f'set_supports_swing_mode_{mode.lower()}') + for trait in traits: + cg.add(getattr(var.config_traits(), trait)(True)) + yield cg.register_component(var, config) yield climate.register_climate(var, config) cg.add_library("https://github.com/SwiCago/HeatPump", None) diff --git a/components/mitsubishi_heatpump/espmhp.cpp b/components/mitsubishi_heatpump/espmhp.cpp index d182fbe..3f00f2a 100644 --- a/components/mitsubishi_heatpump/espmhp.cpp +++ b/components/mitsubishi_heatpump/espmhp.cpp @@ -32,7 +32,15 @@ MitsubishiHeatPump::MitsubishiHeatPump( ) : PollingComponent{poll_interval}, // member initializers list hw_serial_{hw_serial} -{ } +{ + this->traits_.set_supports_action(true); + this->traits_.set_supports_current_temperature(true); + this->traits_.set_supports_two_point_target_temperature(false); + this->traits_.set_supports_away(false); + this->traits_.set_visual_min_temperature(ESPMHP_MIN_TEMPERATURE); + this->traits_.set_visual_max_temperature(ESPMHP_MAX_TEMPERATURE); + this->traits_.set_visual_temperature_step(ESPMHP_TEMPERATURE_STEP); +} void MitsubishiHeatPump::check_logger_conflict_() { #ifdef USE_LOGGER @@ -57,7 +65,7 @@ void MitsubishiHeatPump::update() { } /** - * Define our supported traits. + * Get our supported traits. * * Note: * Many of the following traits are only available in the 1.5.0 dev train of @@ -67,33 +75,17 @@ void MitsubishiHeatPump::update() { * This class' supported climate::ClimateTraits. */ climate::ClimateTraits MitsubishiHeatPump::traits() { - auto traits = climate::ClimateTraits(); - traits.set_supports_action(true); - traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); - traits.set_supports_cool_mode(true); - traits.set_supports_heat_mode(true); - traits.set_supports_dry_mode(true); - traits.set_supports_fan_only_mode(true); - traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); - traits.set_visual_min_temperature(ESPMHP_MIN_TEMPERATURE); - traits.set_visual_max_temperature(ESPMHP_MAX_TEMPERATURE); - traits.set_visual_temperature_step(ESPMHP_TEMPERATURE_STEP); - traits.set_supports_fan_mode_on(false); - traits.set_supports_fan_mode_off(false); - traits.set_supports_fan_mode_auto(true); - traits.set_supports_fan_mode_focus(false); - traits.set_supports_fan_mode_diffuse(true); - traits.set_supports_fan_mode_low(true); - traits.set_supports_fan_mode_medium(true); - traits.set_supports_fan_mode_middle(true); - traits.set_supports_fan_mode_high(true); - traits.set_supports_swing_mode_off(true); - traits.set_supports_swing_mode_both(false); - traits.set_supports_swing_mode_vertical(true); - traits.set_supports_swing_mode_horizontal(false); - return traits; + return traits_; +} + +/** + * Modify our supported traits. + * + * Returns: + * A reference to this class' supported climate::ClimateTraits. + */ +climate::ClimateTraits& MitsubishiHeatPump::config_traits() { + return traits_; } /** diff --git a/components/mitsubishi_heatpump/espmhp.h b/components/mitsubishi_heatpump/espmhp.h index ee431d3..b13097d 100644 --- a/components/mitsubishi_heatpump/espmhp.h +++ b/components/mitsubishi_heatpump/espmhp.h @@ -80,6 +80,9 @@ class MitsubishiHeatPump : public PollingComponent, public climate::Climate { // Configure the climate object with traits that we support. climate::ClimateTraits traits() override; + // Get a mutable reference to the traits that we support. + climate::ClimateTraits& config_traits(); + // Debugging function to print the object's state. void dump_state(); @@ -90,6 +93,9 @@ class MitsubishiHeatPump : public PollingComponent, public climate::Climate { // HeatPump object using the underlying Arduino library. HeatPump* hp; + // The ClimateTraits supported by this HeatPump. + climate::ClimateTraits traits_; + // Allow the HeatPump class to use get_hw_serial_ friend class HeatPump; From 83c00b8d109fd5fe7e49f0140b0520862e3355d8 Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Wed, 26 May 2021 21:20:11 +1200 Subject: [PATCH 04/10] Optionally set baud_rate in YAML Some models require different baud rates (e.g. https://github.com/SwiCago/HeatPump/pull/158). --- components/mitsubishi_heatpump/__init__.py | 5 +++++ components/mitsubishi_heatpump/espmhp.cpp | 6 +++++- components/mitsubishi_heatpump/espmhp.h | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/components/mitsubishi_heatpump/__init__.py b/components/mitsubishi_heatpump/__init__.py index c134dea..1f5bfa4 100644 --- a/components/mitsubishi_heatpump/__init__.py +++ b/components/mitsubishi_heatpump/__init__.py @@ -5,6 +5,7 @@ from esphome.components.logger import HARDWARE_UART_TO_SERIAL from esphome.const import ( CONF_ID, CONF_HARDWARE_UART, + CONF_BAUD_RATE, CONF_UPDATE_INTERVAL, CONF_MODE, CONF_FAN_MODE, @@ -37,6 +38,7 @@ CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MitsubishiHeatPump), cv.Optional(CONF_HARDWARE_UART, default="UART0"): valid_uart, + cv.Optional(CONF_BAUD_RATE): cv.positive_int, # If polling interval is greater than 9 seconds, the HeatPump library # reconnects, but doesn't then follow up with our data request. @@ -65,6 +67,9 @@ def to_code(config): serial = HARDWARE_UART_TO_SERIAL[config[CONF_HARDWARE_UART]] var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f'&{serial}')) + if CONF_BAUD_RATE in config: + cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) + traits = [] for mode in config[CONF_SUPPORTS][CONF_MODE]: if mode == 'OFF': diff --git a/components/mitsubishi_heatpump/espmhp.cpp b/components/mitsubishi_heatpump/espmhp.cpp index 3f00f2a..138af06 100644 --- a/components/mitsubishi_heatpump/espmhp.cpp +++ b/components/mitsubishi_heatpump/espmhp.cpp @@ -64,6 +64,10 @@ void MitsubishiHeatPump::update() { #endif } +void MitsubishiHeatPump::set_baud_rate(int baud) { + this->baud_ = baud; +} + /** * Get our supported traits. * @@ -443,7 +447,7 @@ void MitsubishiHeatPump::setup() { ESP_LOGCONFIG(TAG, "Calling hp->connect(%p)", this->get_hw_serial_()); - if (hp->connect(this->get_hw_serial_())) { + if (hp->connect(this->get_hw_serial_(), this->baud_)) { hp->sync(); } else { diff --git a/components/mitsubishi_heatpump/espmhp.h b/components/mitsubishi_heatpump/espmhp.h index b13097d..be2883d 100644 --- a/components/mitsubishi_heatpump/espmhp.h +++ b/components/mitsubishi_heatpump/espmhp.h @@ -62,6 +62,9 @@ class MitsubishiHeatPump : public PollingComponent, public climate::Climate { ESPMHP_VERSION); } + // Set the baud rate. Must be called before setup() to have any effect. + void set_baud_rate(int); + // print the current configuration void dump_config() override; @@ -124,7 +127,7 @@ class MitsubishiHeatPump : public PollingComponent, public climate::Climate { private: // Retrieve the HardwareSerial pointer from friend and subclasses. HardwareSerial *hw_serial_; - + int baud_ = 0; }; #endif From cad0aee55e5fa818e66a8556a8cc2c42c81909ff Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Wed, 26 May 2021 21:51:43 +1200 Subject: [PATCH 05/10] Document the new configuration options --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 1605c50..24036df 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,27 @@ mitsubishi_heatpump: hardware_uart: UART0 ``` +# Advanced + +Some models of heat pump require different baud rates or don't support all +possible modes of operation. You can configure pretty much everything in YAML +to match what your hardware supports. For example: + +```yaml +mitsubishi_heatpump: + name: "My heat pump" + hardware_uart: UART2 + baud_rate: 9600 + supports: + mode: [AUTO, COOL, HEAT, FAN_ONLY] + fan_mode: [AUTO, LOW, MEDIUM, HIGH] + swing_mode: [OFF, VERTICAL] + visual: + min_temperature: 16 + max_temperature: 31 + temperature_step: 1.0 +``` + # See Also ## Other Implementations From cc4022babbcd16921068197d08b8a037d0dfba56 Mon Sep 17 00:00:00 2001 From: Simon Knopp Date: Thu, 27 May 2021 10:44:07 +1200 Subject: [PATCH 06/10] Make this component work as a climate platform I didn't understand initially how this was meant to be done. It makes sense in hindsight. --- README.md | 51 +++++++------ components/mitsubishi_heatpump/__init__.py | 88 ---------------------- components/mitsubishi_heatpump/climate.py | 88 ++++++++++++++++++++++ 3 files changed, 115 insertions(+), 112 deletions(-) create mode 100644 components/mitsubishi_heatpump/climate.py diff --git a/README.md b/README.md index 24036df..d7792be 100644 --- a/README.md +++ b/README.md @@ -69,14 +69,15 @@ external_components: Add a `mitsubishi_heatpump` to your ESPHome config: ```yaml -mitsubishi_heatpump: - name: "My Heat Pump" +climate: + - platform: mitsubishi_heatpump + name: "My Heat Pump" - # Optional - hardware_uart: UART0 + # Optional + hardware_uart: UART0 - # Optional - update_period: 500ms + # Optional + update_period: 500ms ``` On ESP8266 you'll need to disable logging to serial because it conflicts with @@ -171,13 +172,14 @@ sensor: external_components: - source: github://geoffdavis/esphome-mitsubishiheatpump -mitsubishi_heatpump: - name: "Den Heat Pump" +climate: + - platform: mitsubishi_heatpump + name: "Den Heat Pump" - # ESP32 only - change UART0 to UART1 or UART2 and remove the - # logging:baud_rate above to allow the built-in UART0 to function for - # logging. - hardware_uart: UART0 + # ESP32 only - change UART0 to UART1 or UART2 and remove the + # logging:baud_rate above to allow the built-in UART0 to function for + # logging. + hardware_uart: UART0 ``` # Advanced @@ -187,18 +189,19 @@ possible modes of operation. You can configure pretty much everything in YAML to match what your hardware supports. For example: ```yaml -mitsubishi_heatpump: - name: "My heat pump" - hardware_uart: UART2 - baud_rate: 9600 - supports: - mode: [AUTO, COOL, HEAT, FAN_ONLY] - fan_mode: [AUTO, LOW, MEDIUM, HIGH] - swing_mode: [OFF, VERTICAL] - visual: - min_temperature: 16 - max_temperature: 31 - temperature_step: 1.0 +climate: + - platform: mitsubishi_heatpump + name: "My heat pump" + hardware_uart: UART2 + baud_rate: 9600 + supports: + mode: [AUTO, COOL, HEAT, FAN_ONLY] + fan_mode: [AUTO, LOW, MEDIUM, HIGH] + swing_mode: [OFF, VERTICAL] + visual: + min_temperature: 16 + max_temperature: 31 + temperature_step: 1.0 ``` # See Also diff --git a/components/mitsubishi_heatpump/__init__.py b/components/mitsubishi_heatpump/__init__.py index 1f5bfa4..e69de29 100644 --- a/components/mitsubishi_heatpump/__init__.py +++ b/components/mitsubishi_heatpump/__init__.py @@ -1,88 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import climate -from esphome.components.logger import HARDWARE_UART_TO_SERIAL -from esphome.const import ( - CONF_ID, - CONF_HARDWARE_UART, - CONF_BAUD_RATE, - CONF_UPDATE_INTERVAL, - CONF_MODE, - CONF_FAN_MODE, - CONF_SWING_MODE, -) -from esphome.core import CORE, coroutine - -AUTO_LOAD = ["climate"] - -CONF_SUPPORTS = "supports" -DEFAULT_CLIMATE_MODES = ['AUTO', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY'] -DEFAULT_FAN_MODES = ['AUTO', 'DIFFUSE', 'LOW', 'MEDIUM', 'MIDDLE', 'HIGH'] -DEFAULT_SWING_MODES = ['OFF', 'VERTICAL'] - -MitsubishiHeatPump = cg.global_ns.class_("MitsubishiHeatPump", climate.Climate, cg.PollingComponent) - - -def valid_uart(uart): - if CORE.is_esp8266: - uarts = ["UART0"] # UART1 is tx-only - elif CORE.is_esp32: - uarts = ["UART0", "UART1", "UART2"] - else: - raise NotImplementedError - - return cv.one_of(*uarts, upper=True)(uart) - - -CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MitsubishiHeatPump), - cv.Optional(CONF_HARDWARE_UART, default="UART0"): valid_uart, - cv.Optional(CONF_BAUD_RATE): cv.positive_int, - - # If polling interval is greater than 9 seconds, the HeatPump library - # reconnects, but doesn't then follow up with our data request. - cv.Optional(CONF_UPDATE_INTERVAL, default="500ms"): cv.All( - cv.update_interval, - cv.Range(max=cv.TimePeriod(milliseconds=9000)) - ), - - # Optionally override the supported ClimateTraits. - cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( - { - cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): - cv.ensure_list(climate.validate_climate_mode), - cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): - cv.ensure_list(climate.validate_climate_fan_mode), - cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): - cv.ensure_list(climate.validate_climate_swing_mode), - } - ), - } -).extend(cv.COMPONENT_SCHEMA) - - -@coroutine -def to_code(config): - serial = HARDWARE_UART_TO_SERIAL[config[CONF_HARDWARE_UART]] - var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f'&{serial}')) - - if CONF_BAUD_RATE in config: - cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) - - traits = [] - for mode in config[CONF_SUPPORTS][CONF_MODE]: - if mode == 'OFF': - continue - traits.append(f'set_supports_{mode.lower()}_mode') - for mode in config[CONF_SUPPORTS][CONF_FAN_MODE]: - traits.append(f'set_supports_fan_mode_{mode.lower()}') - for mode in config[CONF_SUPPORTS][CONF_SWING_MODE]: - traits.append(f'set_supports_swing_mode_{mode.lower()}') - for trait in traits: - cg.add(getattr(var.config_traits(), trait)(True)) - - yield cg.register_component(var, config) - yield climate.register_climate(var, config) - cg.add_library("https://github.com/SwiCago/HeatPump", None) - diff --git a/components/mitsubishi_heatpump/climate.py b/components/mitsubishi_heatpump/climate.py new file mode 100644 index 0000000..1f5bfa4 --- /dev/null +++ b/components/mitsubishi_heatpump/climate.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.components.logger import HARDWARE_UART_TO_SERIAL +from esphome.const import ( + CONF_ID, + CONF_HARDWARE_UART, + CONF_BAUD_RATE, + CONF_UPDATE_INTERVAL, + CONF_MODE, + CONF_FAN_MODE, + CONF_SWING_MODE, +) +from esphome.core import CORE, coroutine + +AUTO_LOAD = ["climate"] + +CONF_SUPPORTS = "supports" +DEFAULT_CLIMATE_MODES = ['AUTO', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY'] +DEFAULT_FAN_MODES = ['AUTO', 'DIFFUSE', 'LOW', 'MEDIUM', 'MIDDLE', 'HIGH'] +DEFAULT_SWING_MODES = ['OFF', 'VERTICAL'] + +MitsubishiHeatPump = cg.global_ns.class_("MitsubishiHeatPump", climate.Climate, cg.PollingComponent) + + +def valid_uart(uart): + if CORE.is_esp8266: + uarts = ["UART0"] # UART1 is tx-only + elif CORE.is_esp32: + uarts = ["UART0", "UART1", "UART2"] + else: + raise NotImplementedError + + return cv.one_of(*uarts, upper=True)(uart) + + +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MitsubishiHeatPump), + cv.Optional(CONF_HARDWARE_UART, default="UART0"): valid_uart, + cv.Optional(CONF_BAUD_RATE): cv.positive_int, + + # If polling interval is greater than 9 seconds, the HeatPump library + # reconnects, but doesn't then follow up with our data request. + cv.Optional(CONF_UPDATE_INTERVAL, default="500ms"): cv.All( + cv.update_interval, + cv.Range(max=cv.TimePeriod(milliseconds=9000)) + ), + + # Optionally override the supported ClimateTraits. + cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( + { + cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): + cv.ensure_list(climate.validate_climate_mode), + cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): + cv.ensure_list(climate.validate_climate_fan_mode), + cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): + cv.ensure_list(climate.validate_climate_swing_mode), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +@coroutine +def to_code(config): + serial = HARDWARE_UART_TO_SERIAL[config[CONF_HARDWARE_UART]] + var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f'&{serial}')) + + if CONF_BAUD_RATE in config: + cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) + + traits = [] + for mode in config[CONF_SUPPORTS][CONF_MODE]: + if mode == 'OFF': + continue + traits.append(f'set_supports_{mode.lower()}_mode') + for mode in config[CONF_SUPPORTS][CONF_FAN_MODE]: + traits.append(f'set_supports_fan_mode_{mode.lower()}') + for mode in config[CONF_SUPPORTS][CONF_SWING_MODE]: + traits.append(f'set_supports_swing_mode_{mode.lower()}') + for trait in traits: + cg.add(getattr(var.config_traits(), trait)(True)) + + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + cg.add_library("https://github.com/SwiCago/HeatPump", None) + From 11ab3f2cac1abdb5f0107fb49d50db7e87e373d2 Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Thu, 27 May 2021 10:40:22 -0700 Subject: [PATCH 07/10] Correct update_interval parameter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2375d98..0360b39 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ climate: hardware_uart: UART0 # Optional - update_period: 500ms + update_interval: 500ms ``` On ESP8266 you'll need to disable logging to serial because it conflicts with From 1aa19b28883c38d692adbb6c08bf91001f33cdc7 Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Thu, 27 May 2021 13:24:02 -0700 Subject: [PATCH 08/10] Minor whitespace cleanup --- components/mitsubishi_heatpump/climate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/mitsubishi_heatpump/climate.py b/components/mitsubishi_heatpump/climate.py index 1f5bfa4..901ef7f 100644 --- a/components/mitsubishi_heatpump/climate.py +++ b/components/mitsubishi_heatpump/climate.py @@ -50,11 +50,11 @@ CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( # Optionally override the supported ClimateTraits. cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( { - cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): + cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): cv.ensure_list(climate.validate_climate_mode), - cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): + cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): cv.ensure_list(climate.validate_climate_fan_mode), - cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): + cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): cv.ensure_list(climate.validate_climate_swing_mode), } ), From c4ea4c14e17f0b9884fea2c531a3d5619c3f6d89 Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Thu, 27 May 2021 13:34:23 -0700 Subject: [PATCH 09/10] Update documentation for external component Add configuration variable reference. Expand upon example configuration. Add instructions for upgrading to version 2.0.x --- README.md | 109 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0360b39..3b3d49c 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ via CN105. ### Step 2: Use ESPHome 1.18.0 or higher The code in this repository makes use of a number of features in the 1.18.0 -version of ESPHome, including various Fan modes and external components. +version of ESPHome, including various Fan modes and +[external components](https://esphome.io/components/external_components.html). ### Step 3: Add this repository as an external component @@ -64,6 +65,36 @@ external_components: - source: github://geoffdavis/esphome-mitsubishiheatpump ``` +#### Step 3a: Upgrading from 1.x releases + +Version 2.0 and greater of this libary use the ESPHome `external_components` +feature, which is a huge step forward in terms of usability. In order to make +things compile correctly, you will need to: +1. Remove the `libraries` section that imports + `https://github.com/SwiCago/HeatPump`, as this is handled by the + `external_component` section of manifest. +2. Remove the `includes` section that imports `src/esphome-mitsubishiheatpump` +3. Delete the old checkout of this repository under + `src/esphome-mitsubishiheatpump`. +4. Clean your old ESPHome build directories out (3-dot menu, "Clean Build + Files") +5. You may also have to delete the _esphomenodename_ directory that + corresponds with your _esphomenodename.yaml_ configuration file + completely. Testing with ESPHome 0.18.x showed this to be necessary to get + the cached copy of src/esphome-mitsubishiheatpump to go away entirely, as + the "Clean Build Files" isn't as thorough as one would like. + +*Note:* Failure to delete the old source directory and remove the `includes` +and `libraries` lines will likely result in compilation errors complaining +about duplicate declarations of `MitsubishiHeatPump::traits()`. + +##### Example error +``` +Linking /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/firmware.elf +/root/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/src/esphome/components/mitsubishi_heatpump/espmhp.cpp.o: in function `MitsubishiHeatPump::traits()': +espmhp.cpp:(.text._ZN18MitsubishiHeatPump6traitsEv+0x4): multiple definition of `MitsubishiHeatPump::traits()'; /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/src/esphome-mitsubishiheatpump/espmhp.cpp.o:espmhp.cpp:(.text._ZN18MitsubishiHeatPump6traitsEv+0x80): first defined here +``` + ### Step 4: Configure the heatpump Add a `mitsubishi_heatpump` to your ESPHome config: @@ -88,16 +119,16 @@ logger: baud_rate: 0 ``` -On ESP32 you can change `hardware_uart` to `UART1` or `UART2` and keep logging +On ESP32 you can change `hardware\_uart` to `UART1` or `UART2` and keep logging enabled on the main serial port. -Note: this component DOES NOT use the ESPHome `uart` component, as it requires -direct access to a hardware UART via the Arduino `HardwareSerial` class. The -Mitsubishi Heatpump units use an atypical serial port setting ("even parity"). -Parity bit support is not implemented in any of the existing software serial -libraries, including the one in ESPHome. There's currently no way to guarantee -access to a hardware UART nor retrieve the `HardwareSerial` handle from the -`uart` component within the ESPHome framework. +*Note:* this component DOES NOT use the ESPHome `uart` component, as it +requires direct access to a hardware UART via the Arduino `HardwareSerial` +class. The Mitsubishi Heatpump units use an atypical serial port setting ("even +parity"). Parity bit support is not implemented in any of the existing +software serial libraries, including the one in ESPHome. There's currently no +way to guarantee access to a hardware UART nor retrieve the `HardwareSerial` +handle from the `uart` component within the ESPHome framework. # Example configuration @@ -122,6 +153,14 @@ wifi: ssid: "Denheatpump Fallback Hotspot" password: !secret fallback_password +# Note: if upgrading from 1.x releases of esphome-mitsubishiheatpump, be sure +# to remove any old entries from the `libraries` and `includes` section. +libraries: + # Remove reference to SwiCago/HeatPump + +includes: + # Remove reference to src/esphome-mitsubishiheatpump + captive_portal: # Enable logging @@ -182,11 +221,11 @@ climate: hardware_uart: UART0 ``` -# Advanced +# Advanced configuration Some models of heat pump require different baud rates or don't support all -possible modes of operation. You can configure pretty much everything in YAML -to match what your hardware supports. For example: +possible modes of operation. You can configure mulitple climate "traits" in +YAML to match what your hardware supports. For example: ```yaml climate: @@ -204,15 +243,44 @@ climate: temperature_step: 1.0 ``` +## Configuration variables that affect this library directly + +* *hardware\_uart* (_Optional_): the hardware UART instance to use for + communcation with the heatpump. On ESP8266, only `UART0` is usable. On ESP32, + `UART0`, `UART1`, and `UART2` are all valid choices. Default: `UART0` +* *baud\_rate* (_Optional_): Serial BAUD rate used to communicate with the + HeatPump. Most systems use the default value of `4800` baud, but some use + `9600`. Default: `4800` +* *update\_interval* (_Optional_, range: 0ms to 9000ms): How often this + component polls the heatpump hardware, in milliseconds. Maximum usable value + is 9 seconds due to underlying issues with the HeatPump library. Default: 500ms +* *supports* (_Optional_): Supported features for the device. ** *mode* + (_Optional_, list): Supported climate modes for the HeatPump. Default: + `['AUTO', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY']` + ** *fan_mode* (_Optional_, list): + Supported fan speeds for the HeatPump. Default: `['AUTO', 'DIFFUSE', 'LOW', + 'MEDIUM', 'MIDDLE', 'HIGH']` ** *swing_mode* (_Optional_, list): Supported + fan swing modes. Most Mitsubishi units only support the default. Default: + `['OFF', 'VERTICAL']` + +## Other configuration + +* *id* (_Optional_): used to identify multiple instances, e.g. "denheatpump" +* *name* (_Required_): The name of the climate component, e.g. "Den Heatpump" +* *visual* (_Optional_): The core `Climate` component has several *visual* + options that can be set. See the [Climate + Component](https://esphome.io/components/climate/index.html) documentation for + details. + # See Also ## Other Implementations The [gysmo38/mitsubishi2MQTT](https://github.com/gysmo38/mitsubishi2MQTT) - Arduino sketch also uses the `SwiCago/HeatPump` -library, and works with MQTT directly. The author found it's WiFi stack to not -be particularly robust, but the controls worked fine. Like this ESPHome -repository, it will automatically register the device in your HomeAssistant -instance if you have HA configured to do so. +Arduino sketch also uses the `SwiCago/HeatPump` +library, and works with MQTT directly. The author of this implementation found +`mitsubishi2MQTT`'s WiFi stack to not be particularly robust, but the controls +worked fine. Like this ESPHome repository, `mitsubishi2MQTT` will automatically +register the device in your HomeAssistant instance if you have HA configured to do so. There's also the built-in to ESPHome [Mitsubishi](https://github.com/esphome/esphome/blob/dev/esphome/components/mitsubishi/mitsubishi.h) @@ -226,6 +294,7 @@ the settings via an IR remote. ## Reference documentation The author referred to the following documentation repeatedly: -* https://esphome.io/components/sensor/custom.html -* https://esphome.io/components/climate/custom.html -* Source for ESPHome's Dev branch: https://github.com/esphome/esphome/tree/dev/esphome/components/climate +* [ESPHome Custom Sensors Reference](https://esphome.io/components/sensor/custom.html) +* [ESPHome Custom Climate Components Reference](https://esphome.io/components/climate/custom.html) +* [ESPHome External Components Reference](https://esphome.io/components/external_components.html) +* [Source for ESPHome's Climate Component](https://github.com/esphome/esphome/tree/master/esphome/components/climate) From 6d57971247fef5a3bd1fc20c1801dd078fd93ae4 Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Thu, 27 May 2021 13:38:25 -0700 Subject: [PATCH 10/10] Bump version to 2.0.0, update authors --- components/mitsubishi_heatpump/espmhp.cpp | 5 +++-- components/mitsubishi_heatpump/espmhp.h | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/mitsubishi_heatpump/espmhp.cpp b/components/mitsubishi_heatpump/espmhp.cpp index 138af06..bd9bb3b 100644 --- a/components/mitsubishi_heatpump/espmhp.cpp +++ b/components/mitsubishi_heatpump/espmhp.cpp @@ -8,12 +8,13 @@ * Author: Barry Loong @loongyh on GitHub. * Author: @am-io on Github. * Author: @nao-pon on Github. - * Last Updated: 2020-07-06 + * Author: Simon Knopp @sijk on Github + * Last Updated: 2021-05-27 * License: BSD * * Requirements: * - https://github.com/SwiCago/HeatPump - * - ESPHome 1.15.0 or greater + * - ESPHome 1.18.0 or greater */ #include "espmhp.h" diff --git a/components/mitsubishi_heatpump/espmhp.h b/components/mitsubishi_heatpump/espmhp.h index be2883d..0a686fe 100644 --- a/components/mitsubishi_heatpump/espmhp.h +++ b/components/mitsubishi_heatpump/espmhp.h @@ -6,12 +6,13 @@ * Author: Geoff Davis * Author: Phil Genera @pgenera on Github. * Author: @nao-pon on Github - * Last Updated: 2021-04-28 + * Author: Simon Knopp @sijk on Github + * Last Updated: 2021-05-27 * License: BSD * * Requirements: * - https://github.com/SwiCago/HeatPump - * - ESPHome 1.15.0 or greater + * - ESPHome 1.18.0 or greater */ #define USE_CALLBACKS @@ -27,7 +28,7 @@ using namespace esphome; static const char* TAG = "MitsubishiHeatPump"; // Logging tag -static const char* ESPMHP_VERSION = "1.3.1"; +static const char* ESPMHP_VERSION = "2.0.0"; /* If polling interval is greater than 9 seconds, the HeatPump library reconnects, but doesn't then follow up with our data request.*/