mirror of
https://github.com/geoffdavis/esphome-mitsubishiheatpump
synced 2024-08-30 18:12:13 +00:00
Initial implementation
This commit is contained in:
parent
d355025436
commit
b1c9234877
365
espmhp.cpp
Normal file
365
espmhp.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
/**
|
||||
* espmhp.cpp
|
||||
*
|
||||
* Implementation of esphome-mitsubishiheatpump
|
||||
*
|
||||
* Author: Geoff Davis.<geoff@geoffdavis.com>
|
||||
* Date: 2020-03-11
|
||||
* License: BSD
|
||||
*
|
||||
* Requirements:
|
||||
* - https://github.com/geoffdavis/HeatPump#init_fix (until upstream is fixed)
|
||||
* - ESPHome 1.5.0-dev or greater
|
||||
*/
|
||||
|
||||
|
||||
#include "espmhp.h"
|
||||
|
||||
void MitsubishiHeatPump::check_logger_conflict_() {
|
||||
#ifdef USE_LOGGER
|
||||
if (this->get_hw_serial_() == logger::global_logger->get_hw_serial()) {
|
||||
ESP_LOGW(TAG, " You're using the same serial port for logging"
|
||||
" and the MitsubishiHeatPump component. Please disable"
|
||||
" logging over the serial port by setting"
|
||||
" logger:baud_rate to 0.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MitsubishiHeatPump::update() {
|
||||
// This will be called every "update_interval" milliseconds.
|
||||
//this->dump_config();
|
||||
hp->sync();
|
||||
#ifndef USE_CALLBACKS
|
||||
this->hpSettingsChanged();
|
||||
heatpumpStatus currentStatus = hp->getStatus();
|
||||
this->hpStatusChanged(currentStatus);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Define our supported traits.
|
||||
*
|
||||
* Note:
|
||||
* Many of the following traits are only available in the 1.5.0 dev train of
|
||||
* ESPHome, particularly the Dry operation mode, and several of the fan modes.
|
||||
*
|
||||
* Returns:
|
||||
* This class' supported ESPHome 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(false);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement control of a MitsubishiHeatPump.
|
||||
*
|
||||
* Maps HomeAssistant/ESPHome modes to Mitsubishi modes.
|
||||
*/
|
||||
void MitsubishiHeatPump::control(const climate::ClimateCall &call) {
|
||||
ESP_LOGV(TAG, "Control called.");
|
||||
|
||||
bool updated = false;
|
||||
if (call.get_mode().has_value()){
|
||||
switch (*call.get_mode()) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
hp->setModeSetting("COOL");
|
||||
hp->setPowerSetting("ON");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
hp->setModeSetting("HEAT");
|
||||
hp->setPowerSetting("ON");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
hp->setModeSetting("DRY");
|
||||
hp->setPowerSetting("ON");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
hp->setModeSetting("AUTO");
|
||||
hp->setPowerSetting("ON");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
hp->setModeSetting("FAN");
|
||||
hp->setPowerSetting("ON");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
hp->setPowerSetting("OFF");
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_target_temperature().has_value()){
|
||||
ESP_LOGV("control", "Sending target temp: %.1f",
|
||||
*call.get_target_temperature())
|
||||
hp->setTemperature(*call.get_target_temperature());
|
||||
updated = true;
|
||||
}
|
||||
|
||||
//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());
|
||||
|
||||
switch(*call.get_fan_mode()) {
|
||||
case climate::CLIMATE_FAN_OFF:
|
||||
hp->setPowerSetting("OFF");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
hp->setPowerSetting("QUIET");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
hp->setPowerSetting("1");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
hp->setPowerSetting("2");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
hp->setPowerSetting("3");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
hp->setPowerSetting("4");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
hp->setFanSpeed("AUTO");
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//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",
|
||||
*call.get_swing_mode());
|
||||
|
||||
switch(*call.get_swing_mode()) {
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
hp->setVaneSetting("AUTO");
|
||||
updated = true;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
hp->setVaneSetting("SWING");
|
||||
updated = true;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "control - received unsupported swing mode request.");
|
||||
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "control - Was HeatPump updated? %s", YESNO(updated));
|
||||
hp->update();
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void MitsubishiHeatPump::hpSettingsChanged() {
|
||||
heatpumpSettings currentSettings = hp->getSettings();
|
||||
|
||||
if (currentSettings.power == NULL) {
|
||||
/*
|
||||
* We should always get a valid pointer here once the HeatPump
|
||||
* component fully initializes. If HeatPump hasn't read the settings
|
||||
* from the unit yet (hp->connect() doesn't do this, sadly), we'll need
|
||||
* to punt on the update. Likely not an issue when run in callback
|
||||
* mode, but that isn't working right yet.
|
||||
*/
|
||||
ESP_LOGW(TAG, "Waiting for HeatPump to read the settings the first time.");
|
||||
delay(10);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* ************ HANDLE POWER AND MODE CHANGES ***********
|
||||
* https://github.com/geoffdavis/HeatPump/blob/stream/src/HeatPump.h#L125
|
||||
* const char* POWER_MAP[2] = {"OFF", "ON"};
|
||||
* const char* MODE_MAP[5] = {"HEAT", "DRY", "COOL", "FAN", "AUTO"};
|
||||
*/
|
||||
if (strcmp(currentSettings.power, "ON") == 0) {
|
||||
if (strcmp(currentSettings.mode, "HEAT") == 0) {
|
||||
this->mode = CLIMATE_MODE_HEAT;
|
||||
} else if (strcmp(currentSettings.mode, "DRY") == 0) {
|
||||
this->mode = CLIMATE_MODE_DRY;
|
||||
} else if (strcmp(currentSettings.mode, "COOL") == 0) {
|
||||
this->mode = CLIMATE_MODE_COOL;
|
||||
} else if (strcmp(currentSettings.mode, "FAN") == 0) {
|
||||
this->mode = CLIMATE_MODE_FAN_ONLY;
|
||||
} else if (strcmp(currentSettings.mode, "AUTO") == 0) {
|
||||
this->mode = CLIMATE_MODE_AUTO;
|
||||
} else {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"Unknown climate mode value %s received from HeatPump",
|
||||
currentSettings.mode
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this->mode = CLIMATE_MODE_OFF;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Climate mode is: %i", this->mode);
|
||||
|
||||
/*
|
||||
* ******* HANDLE FAN CHANGES ********
|
||||
*
|
||||
* const char* FAN_MAP[6] = {"AUTO", "QUIET", "1", "2", "3", "4"};
|
||||
*/
|
||||
if (strcmp(currentSettings.fan, "QUIET") == 0) {
|
||||
this->fan_mode = CLIMATE_FAN_DIFFUSE;
|
||||
} else if (strcmp(currentSettings.fan, "1") == 0) {
|
||||
this->fan_mode = CLIMATE_FAN_LOW;
|
||||
} else if (strcmp(currentSettings.fan, "2") == 0) {
|
||||
this->fan_mode = CLIMATE_FAN_MEDIUM;
|
||||
} else if (strcmp(currentSettings.fan, "3") == 0) {
|
||||
this->fan_mode = CLIMATE_FAN_MIDDLE;
|
||||
} else if (strcmp(currentSettings.fan, "4") == 0) {
|
||||
this->fan_mode = CLIMATE_FAN_HIGH;
|
||||
} else { //case "AUTO" or default:
|
||||
this->fan_mode = CLIMATE_FAN_AUTO;
|
||||
}
|
||||
ESP_LOGI(TAG, "Fan mode is: %i", this->fan_mode);
|
||||
|
||||
/* ******** HANDLE MITSUBISHI VANE CHANGES ********
|
||||
* const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"};
|
||||
*/
|
||||
if (
|
||||
(strcmp(currentSettings.vane, "AUTO") == 0)
|
||||
|| (strcmp(currentSettings.vane, "SWING") == 0)
|
||||
) {
|
||||
this->swing_mode = CLIMATE_SWING_VERTICAL;
|
||||
}
|
||||
else {
|
||||
this->swing_mode = CLIMATE_SWING_OFF;
|
||||
}
|
||||
ESP_LOGI(TAG, "Swing mode is: %i", this->swing_mode);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ******** HANDLE TARGET TEMPERATURE CHANGES ********
|
||||
*/
|
||||
this->target_temperature = currentSettings.temperature;
|
||||
ESP_LOGI(TAG, "Target temp is: %f", this->target_temperature);
|
||||
|
||||
|
||||
/*
|
||||
* ******** Publish state back to ESPHome. ********
|
||||
*/
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Report changes in the current temperature sensed by the HeatPump.
|
||||
*/
|
||||
void MitsubishiHeatPump::hpStatusChanged(heatpumpStatus currentStatus) {
|
||||
this->current_temperature = currentStatus.roomTemperature;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void MitsubishiHeatPump::setup() {
|
||||
// This will be called by App.setup()
|
||||
this->banner();
|
||||
ESP_LOGCONFIG(TAG, "Setting up UART...");
|
||||
if (!this->get_hw_serial_()) {
|
||||
ESP_LOGCONFIG(
|
||||
TAG,
|
||||
"No HardwareSerial was provided. "
|
||||
"Software serial ports are unsupported by this component."
|
||||
);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->check_logger_conflict_();
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Intializing new HeatPump object.");
|
||||
this->hp = new HeatPump();
|
||||
|
||||
#ifdef USE_CALLBACKS
|
||||
hp->setSettingsChangedCallback(
|
||||
[this]() {
|
||||
this->hpSettingsChanged();
|
||||
}
|
||||
);
|
||||
|
||||
hp->setStatusChangedCallback(
|
||||
[this](heatpumpStatus currentStatus) {
|
||||
this->hpStatusChanged(currentStatus);
|
||||
}
|
||||
);
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(
|
||||
TAG,
|
||||
"hw_serial(%p) is &Serial(%p)? %s",
|
||||
this->get_hw_serial_(),
|
||||
&Serial,
|
||||
YESNO(this->get_hw_serial_() == &Serial)
|
||||
);
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Calling hp->connect(%p)", this->get_hw_serial_());
|
||||
|
||||
if (hp->connect(this->get_hw_serial_())) {
|
||||
hp->sync();
|
||||
}
|
||||
else {
|
||||
ESP_LOGCONFIG(
|
||||
TAG,
|
||||
"Connection to HeatPump failed."
|
||||
" Marking MitsubishiHeatPump component as failed."
|
||||
);
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
this->dump_config();
|
||||
}
|
||||
|
||||
void MitsubishiHeatPump::dump_config() {
|
||||
this->banner();
|
||||
ESP_LOGI(TAG, " Supports HEAT: %s", YESNO(true));
|
||||
ESP_LOGI(TAG, " Supports COOL: %s", YESNO(true));
|
||||
ESP_LOGI(TAG, " Supports AWAY mode: %s", YESNO(false));
|
||||
}
|
||||
|
||||
void MitsubishiHeatPump::dump_state() {
|
||||
LOG_CLIMATE("", "MitsubishiHeatPump Climate", this);
|
||||
ESP_LOGI(TAG, "HELLO");
|
||||
}
|
109
espmhp.h
Normal file
109
espmhp.h
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* espmhp.h
|
||||
*
|
||||
* Header file for esphome-mitsubishiheatpump
|
||||
*
|
||||
* Author: Geoff Davis.<geoff@geoffdavis.com>
|
||||
* Date: 2020-03-11
|
||||
* License: BSD
|
||||
*
|
||||
* Requirements:
|
||||
* - https://github.com/geoffdavis/HeatPump#init_fix (until upstream is fixed)
|
||||
* - ESPHome 1.5.0-dev or greater
|
||||
*/
|
||||
|
||||
// Uncomment to use HeatPump callback functions (broken, causes boot failures)
|
||||
//#define USE_CALLBACKS
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/climate.h"
|
||||
#include "esphome/components/climate_traits.h"
|
||||
#include "esphome/components/climate_mode.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "HeatPump.h"
|
||||
|
||||
static const char *TAG = "MitsubishiHeatPump"; // Logging tag
|
||||
|
||||
static const char *ESPMHP_VERSION = "1.0.0-dev";
|
||||
|
||||
/* If polling interval is greater than 9 seconds, the HeatPump
|
||||
library reconnects, but doesn't then follow up with our data request.*/
|
||||
static const uint32_t ESPMHP_POLL_INTERVAL_DEFAULT = 2000; // in milliseconds,
|
||||
// 0 < X <= 9000
|
||||
static const uint8_t ESPMHP_MIN_TEMPERATURE = 16; // degrees C,
|
||||
// defined by hardware
|
||||
static const uint8_t ESPMHP_MAX_TEMPERATURE = 31; // degrees C,
|
||||
//defined by hardware
|
||||
static const uint8_t ESPMHP_TEMPERATURE_STEP = 0.5; // temperature setting step,
|
||||
// in degrees C
|
||||
|
||||
class MitsubishiHeatPump : public PollingComponent, public climate::Climate {
|
||||
|
||||
public:
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MitsubishiHeatPump object
|
||||
*
|
||||
* Args:
|
||||
* hw_serial: pointer to an Arduino HardwareSerial instance
|
||||
* poll_interval: polling interval in milliseconds
|
||||
*/
|
||||
MitsubishiHeatPump(
|
||||
HardwareSerial * hw_serial, uint32_t
|
||||
poll_interval=ESPMHP_POLL_INTERVAL_DEFAULT
|
||||
) : PollingComponent(poll_interval) {
|
||||
this->hw_serial_ = hw_serial;
|
||||
}
|
||||
|
||||
// Print a banner with library information.
|
||||
void banner() {
|
||||
ESP_LOGI(TAG, "ESPHome MitsubishiHeatPump version %s",
|
||||
ESPMHP_VERSION);
|
||||
|
||||
// print the current configuration
|
||||
void dump_config() override;
|
||||
|
||||
// handle a change in settings as detected by the HeatPump library.
|
||||
void hpSettingsChanged();
|
||||
|
||||
// Handle a change in status as detected by the HeatPump library.
|
||||
void hpStatusChanged(heatpumpStatus currentStatus);
|
||||
|
||||
// Set up the component, initializing the HeatPump object.
|
||||
void setup() override;
|
||||
|
||||
// This is called every poll_interval.
|
||||
void update() override;
|
||||
|
||||
// Configure the climate object with traits that we support.
|
||||
climate::ClimateTraits traits() override;
|
||||
|
||||
// Debugging function to print the object's state.
|
||||
void dump_state();
|
||||
|
||||
// Handle a request from the user to change settings.
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
|
||||
protected:
|
||||
// HeatPump object using the underlying Arduino library.
|
||||
HeatPump* hp;
|
||||
|
||||
// Allow the HeatPump class to use get_hw_serial_
|
||||
friend class HeatPump;
|
||||
|
||||
//Accessor method for the HardwareSerial pointer
|
||||
HardwareSerial* get_hw_serial_() {
|
||||
return this->hw_serial_;
|
||||
}
|
||||
|
||||
//Print a warning message if we're using the sole hardware UART on an
|
||||
//ESP8266 or UART0 on ESP32
|
||||
void check_logger_conflict_();
|
||||
|
||||
private:
|
||||
// Retrieve the HardwareSerial pointer from friend and subclasses.
|
||||
HardwareSerial *hw_serial_{nullptr};
|
||||
|
||||
};
|
Loading…
Reference in New Issue
Block a user