Skip to content

Commit

Permalink
Kumo Emulation Mode WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
KazWolfe committed Jun 19, 2024
1 parent bd0ccae commit 68658a9
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 45 deletions.
38 changes: 34 additions & 4 deletions esphome/components/mitsubishi_itp/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from esphome.components import (
climate,
uart,
time,
sensor,
binary_sensor,
button,
Expand All @@ -19,11 +20,13 @@
CONF_SUPPORTED_MODES,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_HUMIDITY,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_NONE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_PERCENT,
)
from esphome.core import coroutine

Expand All @@ -39,6 +42,7 @@
]
DEPENDENCIES = [
"uart",
"time",
"climate",
"sensor",
"binary_sensor",
Expand All @@ -49,8 +53,11 @@

CONF_UART_HEATPUMP = "uart_heatpump"
CONF_UART_THERMOSTAT = "uart_thermostat"
CONF_TIME_SOURCE = "time_source"

CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature"
CONF_THERMOSTAT_HUMIDITY = "thermostat_humidity"
CONF_THERMOSTAT_BATTERY = "thermostat_battery"
CONF_ERROR_CODE = "error_code"
CONF_ISEE_STATUS = "isee_status"

Expand Down Expand Up @@ -104,6 +111,7 @@
cv.GenerateID(CONF_ID): cv.declare_id(MitsubishiUART),
cv.Required(CONF_UART_HEATPUMP): cv.use_id(uart.UARTComponent),
cv.Optional(CONF_UART_THERMOSTAT): cv.use_id(uart.UARTComponent),
cv.Optional(CONF_TIME_SOURCE): cv.use_id(time.RealTimeClock),
# Overwrite name from ENTITY_BASE_SCHEMA with "Climate" as default
cv.Optional(CONF_NAME, default="Climate"): cv.Any(
cv.All(
Expand Down Expand Up @@ -154,6 +162,23 @@
),
sensor.register_sensor,
),
CONF_THERMOSTAT_HUMIDITY: (
"Thermostat Humidity",
sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
sensor.register_sensor
),
CONF_THERMOSTAT_BATTERY: (
"Thermostat Battery",
text_sensor.text_sensor_schema(
icon="mdi:battery",
),
text_sensor.register_text_sensor
),
"compressor_frequency": (
"Compressor Frequency",
sensor.sensor_schema(
Expand Down Expand Up @@ -308,8 +333,14 @@ async def to_code(config):
# Add sensor as source
SELECTS[CONF_TEMPERATURE_SOURCE_SELECT][2].append("Thermostat")

# Traits
# If RTC defined
if CONF_TIME_SOURCE in config:
rtc_component = await cg.get_variable(config[CONF_TIME_SOURCE])
cg.add(getattr(muart_component, "set_time_source")(rtc_component))
elif CONF_UART_THERMOSTAT in config:
raise cv.RequiredFieldInvalid(f"{CONF_TIME_SOURCE} is required if {CONF_TS_UART} is set.")

# Traits
traits = muart_component.config_traits()

if CONF_SUPPORTED_MODES in config:
Expand All @@ -329,9 +360,8 @@ async def to_code(config):
registration_function,
) in SENSORS.items():
# Only add the thermostat temp if we have a TS_UART
if (sensor_designator == CONF_THERMOSTAT_TEMPERATURE) and (
CONF_UART_THERMOSTAT not in config
):
if ((CONF_UART_THERMOSTAT not in config) and
(sensor_designator in [CONF_THERMOSTAT_TEMPERATURE, CONF_THERMOSTAT_HUMIDITY, CONF_THERMOSTAT_BATTERY])):
continue

sensor_conf = config[CONF_SENSORS][sensor_designator]
Expand Down
21 changes: 21 additions & 0 deletions esphome/components/mitsubishi_itp/mitsubishi_uart-climatecall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,27 @@ void MitsubishiUART::control(const climate::ClimateCall &call) {
if (call.get_target_temperature().has_value()) {
target_temperature = call.get_target_temperature().value();
set_request_packet.set_target_temperature(call.get_target_temperature().value());

// update our MHK tracking setpoints accordingly
switch (mode) {
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_DRY:
this->last_cool_setpoint_ = target_temperature;
break;
case climate::CLIMATE_MODE_HEAT:
this->last_heat_setpoint_ = target_temperature;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
if (this->get_traits().get_supports_two_point_target_temperature()) {
this->last_heat_setpoint_ = target_temperature_low;
this->last_cool_setpoint_ = target_temperature_high;
} else {
// this->last_heat_setpoint_ = target_temperature;
// this->last_cool_setpoint_ = target_temperature;
}
default:
break;
}
}

// TODO:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,17 @@ void MitsubishiUART::process_packet(const ExtendedConnectResponsePacket &packet)

void MitsubishiUART::process_packet(const GetRequestPacket &packet) {
ESP_LOGV(TAG, "Processing %s", packet.to_string().c_str());
route_packet_(packet);
// These are just requests for information from the thermostat. For now, nothing to be done
// except route them. In the future, we could use this to inject information for the thermostat
// or use a cached value.

switch (packet.get_requested_command()) {
case GetCommand::KUMO_GET_ADAPTER_STATE:
this->handle_kumo_adapter_state_get_request(packet);
break;
case GetCommand::KUMO_AB:
this->handle_kumo_aa_get_request(packet);
break;
default:
route_packet_(packet);
}
}

void MitsubishiUART::process_packet(const SettingsGetResponsePacket &packet) {
Expand Down Expand Up @@ -111,6 +118,18 @@ void MitsubishiUART::process_packet(const SettingsGetResponsePacket &packet) {
target_temperature = packet.get_target_temp();
publish_on_update_ |= (old_target_temperature != target_temperature);

switch (mode) {
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_DRY:
this->last_cool_setpoint_ = target_temperature;
break;
case climate::CLIMATE_MODE_HEAT:
this->last_heat_setpoint_ = target_temperature;
break;
default:
break;
}

// Fan
static bool fan_changed = false;
switch (packet.get_fan()) {
Expand Down Expand Up @@ -333,6 +352,13 @@ void MitsubishiUART::process_packet(const ErrorStateGetResponsePacket &packet) {
publish_on_update_ |= (old_error_code != error_code_sensor_->raw_state);
}

void MitsubishiUART::process_packet(const SettingsSetRequestPacket &packet) {
ESP_LOGV(TAG, "Processing %s", packet.to_string().c_str());

// forward this packet as-is; we're just intercepting to log.
route_packet_(packet);
}

void MitsubishiUART::process_packet(const RemoteTemperatureSetRequestPacket &packet) {
ESP_LOGV(TAG, "Processing %s", packet.to_string().c_str());

Expand All @@ -355,12 +381,75 @@ void MitsubishiUART::process_packet(const RemoteTemperatureSetRequestPacket &pac

publish_on_update_ |= (old_thermostat_temp != thermostat_temperature_sensor_->raw_state);
}
};
}

void MitsubishiUART::process_packet(const KumoThermostatSensorStatusPacket &packet) {
ESP_LOGV(TAG, "Processing inbound %s", packet.to_string().c_str());

if (thermostat_humidity_sensor_ && packet.get_flags() & 0x04) {
const float old_humidity = thermostat_humidity_sensor_->raw_state;
thermostat_humidity_sensor_->raw_state = packet.get_indoor_humidity_percent();
publish_on_update_ |= (old_humidity != thermostat_humidity_sensor_->raw_state);
}

if (thermostat_battery_sensor_ && packet.get_flags() & 0x08) {
const auto old_battery = thermostat_battery_sensor_->raw_state;
thermostat_battery_sensor_->raw_state = THERMOSTAT_BATTERY_STATE_NAMES[packet.get_thermostat_battery_state()];
publish_on_update_ |= (old_battery != thermostat_battery_sensor_->raw_state);
}

ts_bridge_->send_packet(SetResponsePacket());
}

void MitsubishiUART::process_packet(const KumoThermostatHelloPacket &packet) {
ESP_LOGV(TAG, "Processing inbound %s", packet.to_string().c_str());

ts_bridge_->send_packet(SetResponsePacket());
}

void MitsubishiUART::process_packet(const KumoThermostatStateSyncPacket &packet) {
ESP_LOGV(TAG, "Processing inbound %s", packet.to_string().c_str());

if (packet.get_flags() & 0x08) this->last_heat_setpoint_ = packet.get_heat_setpoint();
if (packet.get_flags() & 0x10) this->last_cool_setpoint_ = packet.get_cool_setpoint();

ts_bridge_->send_packet(SetResponsePacket());
}

void MitsubishiUART::process_packet(const KumoAASetRequestPacket &packet) {
ESP_LOGV(TAG, "Processing inbound KumoAASetRequestPacket: %s", packet.to_string().c_str());

ts_bridge_->send_packet(SetResponsePacket());
}

void MitsubishiUART::process_packet(const SetResponsePacket &packet) {
ESP_LOGV(TAG, "Got Set Response packet, success = %s (code = %x)", packet.is_successful() ? "true" : "false",
packet.get_result_code());
route_packet_(packet);
}

// Process incoming data requests (Kumo)
void MitsubishiUART::handle_kumo_adapter_state_get_request(const GetRequestPacket &packet) {
auto response = KumoCloudStateSyncPacket();

response.set_heat_setpoint(this->last_heat_setpoint_);
response.set_cool_setpoint(this->last_cool_setpoint_);

if (this->time_source != nullptr) {
response.set_timestamp(this->time_source->now());
} else {
ESP_LOGW(TAG, "No time source specified. Cannot provide accurate time!");
response.set_timestamp(ESPTime::from_epoch_utc(1704067200)); // 2024-01-01 00:00:00 Z
}

ts_bridge_->send_packet(response);
}

void MitsubishiUART::handle_kumo_aa_get_request(const GetRequestPacket &packet) {
auto response = KumoABGetRequestPacket();

ts_bridge_->send_packet(response);
}

} // namespace mitsubishi_itp
} // namespace esphome
9 changes: 9 additions & 0 deletions esphome/components/mitsubishi_itp/mitsubishi_uart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ void MitsubishiUART::do_publish_() {
ESP_LOGI(TAG, "Outdoor temp differs, do publish");
outdoor_temperature_sensor_->publish_state(outdoor_temperature_sensor_->raw_state);
}
if (thermostat_humidity_sensor_ &&
(thermostat_humidity_sensor_->raw_state != thermostat_humidity_sensor_->state)) {
ESP_LOGI(TAG, "Thermostat humidity differs, do publish");
thermostat_humidity_sensor_->publish_state(thermostat_humidity_sensor_->raw_state);
}
if (compressor_frequency_sensor_ &&
(compressor_frequency_sensor_->raw_state != compressor_frequency_sensor_->state)) {
ESP_LOGI(TAG, "Compressor frequency differs, do publish");
Expand All @@ -216,6 +221,10 @@ void MitsubishiUART::do_publish_() {
ESP_LOGI(TAG, "Error code state differs, do publish");
error_code_sensor_->publish_state(error_code_sensor_->raw_state);
}
if (thermostat_battery_sensor_ && (thermostat_battery_sensor_->raw_state != thermostat_battery_sensor_->state)) {
ESP_LOGI(TAG, "Thermostat battery state differs, do publish");
thermostat_battery_sensor_->publish_state(thermostat_battery_sensor_->raw_state);
}

// Binary sensors automatically dedup publishes (I think) and so will only actually publish on change
filter_status_sensor_->publish_state(filter_status_sensor_->state);
Expand Down
23 changes: 23 additions & 0 deletions esphome/components/mitsubishi_itp/mitsubishi_uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/time/real_time_clock.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/select/select.h"
#include "esphome/components/sensor/sensor.h"
Expand Down Expand Up @@ -32,6 +33,8 @@ const std::string TEMPERATURE_SOURCE_THERMOSTAT = "Thermostat";
const std::array<std::string, 7> ACTUAL_FAN_SPEED_NAMES = {"Off", "Very Low", "Low", "Medium",
"High", FAN_MODE_VERYHIGH, "Quiet"};

const std::array<std::string, 5> THERMOSTAT_BATTERY_STATE_NAMES = {"OK", "Low", "Critical", "Replace", "Unknown"};

class MitsubishiUART : public PollingComponent, public climate::Climate, public PacketProcessor {
public:
/**
Expand Down Expand Up @@ -69,6 +72,7 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public
// Sensor setters
void set_thermostat_temperature_sensor(sensor::Sensor *sensor) { thermostat_temperature_sensor_ = sensor; };
void set_outdoor_temperature_sensor(sensor::Sensor *sensor) { outdoor_temperature_sensor_ = sensor; };
void set_thermostat_humidity_sensor(sensor::Sensor *sensor) { thermostat_humidity_sensor_ = sensor; }
void set_compressor_frequency_sensor(sensor::Sensor *sensor) { compressor_frequency_sensor_ = sensor; };
void set_actual_fan_sensor(text_sensor::TextSensor *sensor) { actual_fan_sensor_ = sensor; };
void set_filter_status_sensor(binary_sensor::BinarySensor *sensor) { filter_status_sensor_ = sensor; };
Expand All @@ -77,6 +81,7 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public
void set_standby_sensor(binary_sensor::BinarySensor *sensor) { standby_sensor_ = sensor; };
void set_isee_status_sensor(binary_sensor::BinarySensor *sensor) { isee_status_sensor_ = sensor; }
void set_error_code_sensor(text_sensor::TextSensor *sensor) { error_code_sensor_ = sensor; };
void set_thermostat_battery_sensor(text_sensor::TextSensor *sensor) { thermostat_battery_sensor_ = sensor; }

// Select setters
void set_temperature_source_select(select::Select *select) { temperature_source_select_ = select; };
Expand All @@ -98,6 +103,8 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public
// Turns on or off actively sending packets
void set_active_mode(const bool active) { active_mode_ = active; };

void set_time_source(time::RealTimeClock *rtc) { time_source = rtc; }

protected:
void route_packet_(const Packet &packet);

Expand All @@ -112,9 +119,17 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public
void process_packet(const StatusGetResponsePacket &packet) override;
void process_packet(const RunStateGetResponsePacket &packet) override;
void process_packet(const ErrorStateGetResponsePacket &packet) override;
void process_packet(const SettingsSetRequestPacket &packet) override;
void process_packet(const RemoteTemperatureSetRequestPacket &packet) override;
void process_packet(const KumoThermostatSensorStatusPacket &packet) override;
void process_packet(const KumoThermostatHelloPacket &packet) override;
void process_packet(const KumoThermostatStateSyncPacket &packet) override;
void process_packet(const KumoAASetRequestPacket &packet) override;
void process_packet(const SetResponsePacket &packet) override;

void handle_kumo_adapter_state_get_request(const GetRequestPacket &packet) override;
void handle_kumo_aa_get_request(const GetRequestPacket &packet) override;

void do_publish_();

private:
Expand Down Expand Up @@ -161,8 +176,12 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public

ESPPreferenceObject preferences_;

// Time Source
time::RealTimeClock *time_source = nullptr;

// Internal sensors
sensor::Sensor *thermostat_temperature_sensor_ = nullptr;
sensor::Sensor *thermostat_humidity_sensor_ = nullptr;
sensor::Sensor *compressor_frequency_sensor_ = nullptr;
sensor::Sensor *outdoor_temperature_sensor_ = nullptr;
text_sensor::TextSensor *actual_fan_sensor_ = nullptr;
Expand All @@ -172,6 +191,7 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public
binary_sensor::BinarySensor *standby_sensor_ = nullptr;
binary_sensor::BinarySensor *isee_status_sensor_ = nullptr;
text_sensor::TextSensor *error_code_sensor_ = nullptr;
text_sensor::TextSensor *thermostat_battery_sensor_ = nullptr;

// Selects
select::Select *temperature_source_select_;
Expand All @@ -185,6 +205,9 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public

void send_if_active_(const Packet &packet);
bool active_mode_ = true;

float last_cool_setpoint_ = NAN;
float last_heat_setpoint_ = NAN;
};

struct MUARTPreferences {
Expand Down
Loading

0 comments on commit 68658a9

Please sign in to comment.