Merge branch 'develop' into develop

This commit is contained in:
Geoff Davis 2023-10-13 13:31:50 -07:00 committed by GitHub
commit af351834ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 213 additions and 29 deletions

109
README.md
View File

@ -4,23 +4,28 @@ Wirelessly control your Mitsubishi Comfort HVAC equipment with an ESP8266 or
ESP32 using the [ESPHome](https://esphome.io) framework.
## Features
* Instant feedback of command changes via RF Remote to HomeAssistant or MQTT.
* Direct control without the remote.
* Uses the [SwiCago/HeatPump](https://github.com/SwiCago/HeatPump) Arduino
libary to talk to the unit directly via the internal `CN105` connector.
## Requirements
* https://github.com/SwiCago/HeatPump
* [SwiCago/HeatPump](https://github.com/SwiCago/HeatPump)
* ESPHome 1.19.1 or greater
## Supported Microcontrollers
This library should work on most ESP8266 or ESP32 platforms. It has been tested
with the following MCUs:
* Generic ESP-01S board (ESP8266)
* WeMos D1 Mini (ESP8266)
* Generic ESP32 Dev Kit (ESP32)
## Supported Mitsubishi Climate Units
The underlying HeatPump library works with a number of Mitsubishi HVAC
units. Basically, if the unit has a `CN105` header on the main board, it should
work with this library. The [HeatPump
@ -34,11 +39,13 @@ available.
The whole integration with this libary and the underlying HeatPump has been
tested by the author on the following units:
* `MSZ-GL06NA`
* `MFZ-KA09NA`
## Usage
### Step 1: Build a control circuit.
### Step 1: Build a control circuit
Build a control circuit with your MCU as detailed in the [SwiCago/HeatPump
README](https://github.com/SwiCago/HeatPump/blob/master/README.md#demo-circuit).
@ -70,6 +77,7 @@ external_components:
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.
@ -81,7 +89,7 @@ things compile correctly, you will need to:
5. You may also have to delete the _esphomenodename_ directory that
corresponds with your _esphomenodename.yaml_ configuration file
completely. This directory may exist in your base config directory,
or in `config/.esphome/build`. Testing with ESPHome 0.18.x showed this
or in `config/.esphome/build`. 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.
@ -90,7 +98,8 @@ and `libraries` lines will likely result in compilation errors complaining
about duplicate declarations of `MitsubishiHeatPump::traits()`.
##### Example error
```
```none
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
@ -139,14 +148,14 @@ 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 configurations
## Example configurations
Below is an example configuration which will include wireless strength
indicators and permit over the air updates. You'll need to create a
`secrets.yaml` file inside of your `esphome` directory with entries for the
various items prefixed with `!secret`.
## ESP8266 Example Configuration
### ESP8266 Example Configuration
```yaml
substitutions:
@ -239,7 +248,7 @@ climate:
baud_rate: 4800
```
## ESP32 Example Configuration
### ESP32 Example Configuration
```yaml
substitutions:
@ -324,7 +333,7 @@ climate:
hardware_uart: UART1
```
# Advanced configuration
### Advanced configuration
Some models of heat pump require different baud rates or don't support all
possible modes of operation. You can configure mulitple climate "traits" in
@ -351,40 +360,54 @@ climate:
## Configuration variables that affect this library directly
* *hardware\_uart* (_Optional_): the hardware UART instance to use for
* `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
* `baud_rate` (_Optional_): Serial BAUD rate used to communicate with the
HeatPump. Most systems use the default value of `4800` baud, but some use
`2400` or `9600`. Check [here](https://github.com/SwiCago/HeatPump/issues/13)
to find discussion of whether your particular model requires a non-default baud rate.
Some ESP32 boards will require the baud_rate setting if
Some ESP32 boards will require the baud_rate setting if
hardware_uart is specified. Default: `4800`.
* *rx\_pin* (_Optional_): pin number to use as RX for the specified hardware
* `rx_pin` (_Optional_): pin number to use as RX for the specified hardware
UART (ESP32 only - ESP8266 hardware UART's pins aren't configurable).
* *tx\_pin* (_Optional_): pin number to use as TX for the specified hardware
* `tx_pin` (_Optional_): pin number to use as TX for the specified hardware
UART (ESP32 only - ESP8266 hardware UART's pins aren't configurable).
* *update\_interval* (_Optional_, range: 0ms to 9000ms): How often this
* `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:
* `supports` (_Optional_): Supported features for the device.
** `mode` (_Optional_, list): Supported climate modes for the HeatPump. Default:
`['HEAT_COOL', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY']`
* *fan_mode* (_Optional_, list): Supported fan speeds for the HeatPump.
** `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
** `swing_mode` (_Optional_, list): Supported fan swing modes. Most Mitsubishi
units only support the default. Default: `['OFF', 'VERTICAL']`
* `remote_temperature_operating_timeout_minutes` (_Optional_): The number of
minutes before a set_remote_temperature request becomes stale, while the
heatpump is heating or cooling. Unless a new set_remote_temperature
request was made within the time duration, the heatpump will revert back to it's
internal temperature sensor.
* `remote_temperature_idle_timeout_minutes` (_Optional_): The number of
minutes before a set_remote_temperature request becomes stale while the heatpump
is idle. Unless a new set_remote_temperature request is made within the time duration,
the heatpump will revert back to it's internal temperature sensor.
* `remote_temperature_ping_timeout_minutes` (_Optional_): The number of
minutes before a set_remote_temperature request becomes stale, if a ping
request wasn't received from your ESPHome controller. This will result
in the heatpump reverting to it's internal temperature sensor if the heatpump
loses it's WiFi connection.
## 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*
* `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.
## Remote temperature
### Remote temperature
It is possible to use an external temperature sensor to tell the heat pump what
the room temperature is, rather than relying on its internal temperature
@ -447,9 +470,44 @@ api:
- lambda: 'id(hp).set_remote_temperature(0);'
```
# See Also
It's also possible to configure timeouts which will revert the heatpump
back to it's internal temperature sensor in the event that an external sensor
becomes unavailable. All three settings are optional, but it's recommended
that you configure both operating and idle timeout. Both can be configured to the same
value.
```yaml
climate:
- platform: mitsubishi_heatpump
remote_temperature_operating_timeout_minutes: 65
remote_temperature_idle_timeout_minutes: 120
remote_temperature_ping_timeout_minutes: 20
api:
services:
- service: ping
then:
- lambda: 'id(hp).ping();'
```
There is an explicit distinction between an operating timeout and an idle timeout.
* **Operating timeout** The heatpump is currently pumping heat, and the expectation is that
the temperature should shift within a certain time period. Recommended value: 60 minutes.
* **Idle timeout** The heatpump is not currently pumping heat, so temperature shifts are expected
to happen less frequently. Recommended value depends on the implementation details of your temperature
sensor. Some will only provide updates on temperature changes, others such as Aqara will provide
an update at least once every hour.
* **Ping timeout** Detects if a connection is lost between HomeAssistant and the heatpump, or if your
home assistant instance is down. Recommended value is 20 minutes, with a ping being sent every 5 minutes.
Do not enable ping timeout until you have the logic in place to call the ping service at a regular interval. You
can view the ESPHome logs to ensure this is taking place.
## See Also
### Other Implementations
## 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 of this implementation found
@ -466,9 +524,10 @@ repository and it's underlying `HeatPump` library allows bi-directional
communication with the Mitsubishi system, and can detect when someone changes
the settings via an IR remote.
## Reference documentation
### Reference documentation
The author referred to the following documentation repeatedly:
* [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)

View File

@ -22,6 +22,11 @@ DEFAULT_CLIMATE_MODES = ["HEAT_COOL", "COOL", "HEAT", "DRY", "FAN_ONLY"]
DEFAULT_FAN_MODES = ["AUTO", "DIFFUSE", "LOW", "MEDIUM", "MIDDLE", "HIGH"]
DEFAULT_SWING_MODES = ["OFF", "VERTICAL"]
# Remote temperature timeout configuration
CONF_REMOTE_OPERATING_TIMEOUT = "remote_temperature_operating_timeout_minutes"
CONF_REMOTE_IDLE_TIMEOUT = "remote_temperature_idle_timeout_minutes"
CONF_REMOTE_PING_TIMEOUT = "remote_temperature_ping_timeout_minutes"
MitsubishiHeatPump = cg.global_ns.class_(
"MitsubishiHeatPump", climate.Climate, cg.PollingComponent
)
@ -43,6 +48,9 @@ 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,
cv.Optional(CONF_REMOTE_OPERATING_TIMEOUT): cv.positive_int,
cv.Optional(CONF_REMOTE_IDLE_TIMEOUT): cv.positive_int,
cv.Optional(CONF_REMOTE_PING_TIMEOUT): cv.positive_int,
cv.Optional(CONF_RX_PIN): cv.positive_int,
cv.Optional(CONF_TX_PIN): cv.positive_int,
# If polling interval is greater than 9 seconds, the HeatPump library
@ -79,6 +87,16 @@ def to_code(config):
if CONF_TX_PIN in config:
cg.add(var.set_tx_pin(config[CONF_TX_PIN]))
if CONF_REMOTE_OPERATING_TIMEOUT in config:
cg.add(var.set_remote_operating_timeout_minutes(config[CONF_REMOTE_OPERATING_TIMEOUT]))
if CONF_REMOTE_IDLE_TIMEOUT in config:
cg.add(var.set_remote_idle_timeout_minutes(config[CONF_REMOTE_IDLE_TIMEOUT]))
if CONF_REMOTE_PING_TIMEOUT in config:
cg.add(var.set_remote_ping_timeout_minutes(config[CONF_REMOTE_PING_TIMEOUT]))
supports = config[CONF_SUPPORTS]
traits = var.config_traits()

View File

@ -9,7 +9,8 @@
* Author: @am-io on Github.
* Author: @nao-pon on Github.
* Author: Simon Knopp @sijk on Github
* Last Updated: 2021-05-27
* Author: Paul Murphy @donutsoft on GitHub
* Last Updated: 2023-04-22
* License: BSD
*
* Requirements:
@ -40,6 +41,10 @@ MitsubishiHeatPump::MitsubishiHeatPump(
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);
// Assume a succesful connection was made to the ESPHome controller on
// launch.
this->ping();
}
void MitsubishiHeatPump::check_logger_conflict_() {
@ -67,6 +72,7 @@ void MitsubishiHeatPump::update() {
heatpumpStatus currentStatus = hp->getStatus();
this->hpStatusChanged(currentStatus);
#endif
this->enforce_remote_temperature_sensor_timeout();
}
void MitsubishiHeatPump::set_baud_rate(int baud) {
@ -119,6 +125,24 @@ void MitsubishiHeatPump::control(const climate::ClimateCall &call) {
if (has_mode){
this->mode = *call.get_mode();
}
if (last_remote_temperature_sensor_update_.has_value()) {
// Some remote temperature sensors will only issue updates when a change
// in temperature occurs.
// Assume a case where the idle sensor timeout is 12hrs and operating
// timeout is 1hr. If the user changes the HP setpoint after 1.5hrs, the
// machine will switch to operating mode, the remote temperature
// reading will expire and the HP will revert to it's internal
// temperature sensor.
// This change ensures that if the user changes the machine setpoint,
// the remote sensor has an opportunity to issue an update to reflect
// the new change in temperature.
last_remote_temperature_sensor_update_ =
std::chrono::steady_clock::now();
}
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
hp->setModeSetting("COOL");
@ -195,7 +219,7 @@ void MitsubishiHeatPump::control(const climate::ClimateCall &call) {
//const char* FAN_MAP[6] = {"AUTO", "QUIET", "1", "2", "3", "4"};
if (call.get_fan_mode().has_value()) {
ESP_LOGV("control", "Requested fan mode is %s", *call.get_fan_mode());
ESP_LOGV("control", "Requested fan mode is %d", *call.get_fan_mode());
this->fan_mode = *call.get_fan_mode();
switch(*call.get_fan_mode()) {
case climate::CLIMATE_FAN_OFF:
@ -233,7 +257,7 @@ void MitsubishiHeatPump::control(const climate::ClimateCall &call) {
//const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"};
if (call.get_swing_mode().has_value()) {
ESP_LOGV(TAG, "control - requested swing mode is %s",
ESP_LOGD(TAG, "control - requested swing mode is %d",
*call.get_swing_mode());
this->swing_mode = *call.get_swing_mode();
@ -341,7 +365,7 @@ void MitsubishiHeatPump::hpSettingsChanged() {
} else { //case "AUTO" or default:
this->fan_mode = climate::CLIMATE_FAN_AUTO;
}
ESP_LOGI(TAG, "Fan mode is: %i", this->fan_mode);
ESP_LOGI(TAG, "Fan mode is: %i", this->fan_mode.value_or(-1));
/* ******** HANDLE MITSUBISHI VANE CHANGES ********
* const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"};
@ -415,14 +439,71 @@ void MitsubishiHeatPump::hpStatusChanged(heatpumpStatus currentStatus) {
this->action = climate::CLIMATE_ACTION_OFF;
}
this->operating_ = currentStatus.operating;
this->publish_state();
}
void MitsubishiHeatPump::set_remote_temperature(float temp) {
ESP_LOGD(TAG, "Setting remote temp: %.1f", temp);
if (temp > 0) {
last_remote_temperature_sensor_update_ =
std::chrono::steady_clock::now();
} else {
last_remote_temperature_sensor_update_.reset();
}
this->hp->setRemoteTemperature(temp);
}
void MitsubishiHeatPump::ping() {
ESP_LOGD(TAG, "Ping request received");
last_ping_request_ = std::chrono::steady_clock::now();
}
void MitsubishiHeatPump::set_remote_operating_timeout_minutes(int minutes) {
ESP_LOGD(TAG, "Setting remote operating timeout time: %d minutes", minutes);
remote_operating_timeout_ = std::chrono::minutes(minutes);
}
void MitsubishiHeatPump::set_remote_idle_timeout_minutes(int minutes) {
ESP_LOGD(TAG, "Setting remote idle timeout time: %d minutes", minutes);
remote_idle_timeout_ = std::chrono::minutes(minutes);
}
void MitsubishiHeatPump::set_remote_ping_timeout_minutes(int minutes) {
ESP_LOGD(TAG, "Setting remote ping timeout time: %d minutes", minutes);
remote_ping_timeout_ = std::chrono::minutes(minutes);
}
void MitsubishiHeatPump::enforce_remote_temperature_sensor_timeout() {
// Handle ping timeouts.
if (remote_ping_timeout_.has_value() && last_ping_request_.has_value()) {
auto time_since_last_ping =
std::chrono::steady_clock::now() - last_ping_request_.value();
if(time_since_last_ping > remote_ping_timeout_.value()) {
ESP_LOGW(TAG, "Ping timeout.");
this->set_remote_temperature(0);
last_ping_request_.reset();
return;
}
}
// Handle set_remote_temperature timeouts.
auto remote_set_temperature_timeout =
this->operating_ ? remote_operating_timeout_ : remote_idle_timeout_;
if (remote_set_temperature_timeout.has_value() &&
last_remote_temperature_sensor_update_.has_value()) {
auto time_since_last_temperature_update =
std::chrono::steady_clock::now() - last_remote_temperature_sensor_update_.value();
if (time_since_last_temperature_update > remote_set_temperature_timeout.value()) {
ESP_LOGW(TAG, "Set remote temperature timeout, operating=%d", this->operating_);
this->set_remote_temperature(0);
return;
}
}
}
void MitsubishiHeatPump::setup() {
// This will be called by App.setup()
this->banner();

View File

@ -19,6 +19,7 @@
#include "esphome.h"
#include "esphome/core/preferences.h"
#include <chrono>
#include "HeatPump.h"
@ -99,6 +100,22 @@ class MitsubishiHeatPump : public esphome::PollingComponent, public esphome::cli
// set_remote_temp(0) to switch back to the internal sensor.
void set_remote_temperature(float);
// Used to validate that a connection is present between the controller
// and this heatpump.
void ping();
// Number of minutes before the heatpump reverts back to the internal
// temperature sensor if the machine is currently operating.
void set_remote_operating_timeout_minutes(int);
// Number of minutes before the heatpump reverts back to the internal
// temperature sensor if the machine is currently idle.
void set_remote_idle_timeout_minutes(int);
// Number of minutes before the heatpump reverts back to the internal
// temperature sensor if a ping isn't received from the controller.
void set_remote_ping_timeout_minutes(int);
protected:
// HeatPump object using the underlying Arduino library.
HeatPump* hp;
@ -132,11 +149,20 @@ class MitsubishiHeatPump : public esphome::PollingComponent, public esphome::cli
static esphome::optional<float> load(esphome::ESPPreferenceObject& storage);
private:
void enforce_remote_temperature_sensor_timeout();
// Retrieve the HardwareSerial pointer from friend and subclasses.
HardwareSerial *hw_serial_;
int baud_ = 0;
int rx_pin_ = -1;
int tx_pin_ = -1;
bool operating_ = false;
std::optional<std::chrono::duration<long long, std::ratio<60>>> remote_operating_timeout_;
std::optional<std::chrono::duration<long long, std::ratio<60>>> remote_idle_timeout_;
std::optional<std::chrono::duration<long long, std::ratio<60>>> remote_ping_timeout_;
std::optional<std::chrono::time_point<std::chrono::steady_clock>> last_remote_temperature_sensor_update_;
std::optional<std::chrono::time_point<std::chrono::steady_clock>> last_ping_request_;
};
#endif