[mitsubishi_cn105] Extract top-level hub (#16987)

This commit is contained in:
Boris Krivonog
2026-08-10 14:02:16 -05:00
committed by GitHub
parent e4d08a73b0
commit 9e78a768a2
20 changed files with 592 additions and 140 deletions
@@ -0,0 +1,137 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL
from esphome.core import ID
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType, TemplateArgsType
CODEOWNERS = ["@crnjan"]
DEPENDENCIES = ["uart"]
DOMAIN = "mitsubishi_cn105"
CONF_MITSUBISHI_CN105_ID = f"{DOMAIN}_id"
CONF_TELEMETRY_REQUEST_MIN_INTERVAL = "telemetry_request_min_interval"
mitsubishi_ns = cg.esphome_ns.namespace(DOMAIN)
MitsubishiCN105Component = mitsubishi_ns.class_(
"MitsubishiCN105Component",
cg.Component,
uart.UARTDevice,
)
SetRemoteTemperatureAction = mitsubishi_ns.class_(
"SetRemoteTemperatureAction",
automation.Action,
cg.Parented.template(MitsubishiCN105Component),
)
ClearRemoteTemperatureAction = mitsubishi_ns.class_(
"ClearRemoteTemperatureAction",
automation.Action,
cg.Parented.template(MitsubishiCN105Component),
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MitsubishiCN105Component),
cv.Optional(CONF_UPDATE_INTERVAL, default="1s"): cv.update_interval,
cv.Optional(
CONF_TELEMETRY_REQUEST_MIN_INTERVAL, default="60s"
): cv.update_interval,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
MITSUBISHI_CN105_DEVICE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MITSUBISHI_CN105_ID): cv.use_id(MitsubishiCN105Component),
}
)
FINAL_VALIDATE_SCHEMA = cv.All(
uart.final_validate_device_schema(
DOMAIN,
require_rx=True,
require_tx=True,
data_bits=8,
parity="EVEN",
stop_bits=1,
)
)
async def register_mitsubishi_cn105_device(var: MockObj, config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_MITSUBISHI_CN105_ID])
cg.add(var.set_parent(parent))
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add(
var.set_telemetry_request_min_interval(
config[CONF_TELEMETRY_REQUEST_MIN_INTERVAL]
)
)
REMOTE_TEMPERATURE_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Component),
cv.Required(CONF_TEMPERATURE): cv.templatable(
cv.All(
cv.temperature,
cv.Range(min=8.0, max=39.5),
)
),
}
)
CLEAR_REMOTE_TEMPERATURE_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Component),
}
)
@automation.register_action(
f"{DOMAIN}.set_remote_temperature",
SetRemoteTemperatureAction,
REMOTE_TEMPERATURE_ACTION_SCHEMA,
synchronous=True,
)
async def remote_temperature_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
temperature = await cg.templatable(config[CONF_TEMPERATURE], args, float)
cg.add(var.set_temperature(temperature))
return var
@automation.register_action(
f"{DOMAIN}.clear_remote_temperature",
ClearRemoteTemperatureAction,
CLEAR_REMOTE_TEMPERATURE_ACTION_SCHEMA,
synchronous=True,
)
async def clear_temperature_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@@ -0,0 +1,23 @@
#pragma once
#include "mitsubishi_cn105_component.h"
#include "esphome/core/automation.h"
namespace esphome::mitsubishi_cn105 {
template<typename... Ts>
class SetRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Component> {
public:
TEMPLATABLE_VALUE(float, temperature)
void play(const Ts &...x) override { this->parent_->set_remote_temperature(this->temperature_.value(x...)); }
};
template<typename... Ts>
class ClearRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Component> {
public:
void play(const Ts &...x) override { this->parent_->clear_remote_temperature(); }
};
} // namespace esphome::mitsubishi_cn105
+182 -58
View File
@@ -1,3 +1,5 @@
import logging
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import climate, uart from esphome.components import climate, uart
@@ -7,126 +9,248 @@ from esphome.const import (
CONF_ID, CONF_ID,
CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_SWING_MODES,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_UART_ID,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import ID from esphome.core import CORE, ID
from esphome.cpp_generator import MockObj from esphome.cpp_generator import MockObj
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType, TemplateArgsType from esphome.types import ConfigType, TemplateArgsType
from . import (
CONF_MITSUBISHI_CN105_ID,
DOMAIN,
MITSUBISHI_CN105_DEVICE_SCHEMA,
MitsubishiCN105Component,
mitsubishi_ns,
register_mitsubishi_cn105_device,
)
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
AUTO_LOAD = ["climate"] AUTO_LOAD = ["climate"]
CODEOWNERS = ["@crnjan"]
_LOGGER = logging.getLogger(__name__)
# Deprecated legacy climate-owned hub option. Remove in 2027.2.0.
CONF_CURRENT_TEMPERATURE_MIN_INTERVAL = "current_temperature_min_interval" CONF_CURRENT_TEMPERATURE_MIN_INTERVAL = "current_temperature_min_interval"
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi_cn105") CONF_LEGACY_MITSUBISHI_CN105_ID = "legacy_mitsubishi_cn105_id"
MitsubishiCN105Climate = mitsubishi_ns.class_( MitsubishiCN105Climate = mitsubishi_ns.class_(
"MitsubishiCN105Climate", "MitsubishiCN105Climate",
climate.Climate, climate.Climate,
cg.Component, cg.Component,
uart.UARTDevice, cg.Parented.template(MitsubishiCN105Component),
) )
SetRemoteTemperatureAction = mitsubishi_ns.class_( # Legacy climate action compatibility. Remove in 2027.2.0.
"SetRemoteTemperatureAction", LegacySetRemoteTemperatureAction = mitsubishi_ns.class_(
"LegacySetRemoteTemperatureAction",
automation.Action, automation.Action,
cg.Parented.template(MitsubishiCN105Climate), cg.Parented.template(MitsubishiCN105Climate),
) )
ClearRemoteTemperatureAction = mitsubishi_ns.class_( # Legacy climate action compatibility. Remove in 2027.2.0.
"ClearRemoteTemperatureAction", LegacyClearRemoteTemperatureAction = mitsubishi_ns.class_(
"LegacyClearRemoteTemperatureAction",
automation.Action, automation.Action,
cg.Parented.template(MitsubishiCN105Climate), cg.Parented.template(MitsubishiCN105Climate),
) )
CONFIG_SCHEMA = (
climate.climate_schema(MitsubishiCN105Climate) # Legacy climate-owned hub compatibility. Remove in 2027.2.0.
.extend(uart.UART_DEVICE_SCHEMA) def _has_top_level_hub_config() -> bool:
return DOMAIN in (CORE.raw_config or {})
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
def _prepare_legacy_hub_config(config: ConfigType) -> ConfigType:
_LOGGER.warning(
"Defining 'climate.mitsubishi_cn105' without a top-level '%s:' hub is "
"deprecated. Declare '%s:' and reference it with '%s:' instead. Will "
"be removed in ESPHome 2027.2.0.",
DOMAIN,
DOMAIN,
CONF_MITSUBISHI_CN105_ID,
)
# Add the hidden hub declaration only for legacy climate-owned configs,
# so normal auto-ID resolution does not see it as a top-level hub.
config[CONF_LEGACY_MITSUBISHI_CN105_ID] = cv.declare_id(MitsubishiCN105Component)(
None
)
return config
_BASE_SCHEMA = climate.climate_schema(MitsubishiCN105Climate).extend(
{
cv.Optional(
CONF_SUPPORTED_SWING_MODES, default="OFF"
): validate_climate_swing_mode,
}
)
_HUB_SCHEMA = _BASE_SCHEMA.extend(MITSUBISHI_CN105_DEVICE_SCHEMA)
# Hub options accepted in the legacy climate-owned configuration. When a
# top-level hub exists, leaving these on the climate is always a migration
# mistake and the generic schema error does not explain where they belong.
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
_LEGACY_HUB_KEYS = (
CONF_CURRENT_TEMPERATURE_MIN_INTERVAL,
CONF_UART_ID,
CONF_UPDATE_INTERVAL,
)
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
def _validate_no_legacy_hub_keys(config: ConfigType) -> ConfigType:
legacy_keys = [key for key in _LEGACY_HUB_KEYS if key in config]
if not legacy_keys:
return config
keys = ", ".join(f"'{key}'" for key in legacy_keys)
message = f"{keys} must be moved under the top-level '{DOMAIN}:' block"
if CONF_CURRENT_TEMPERATURE_MIN_INTERVAL in legacy_keys:
message += (
f"; rename '{CONF_CURRENT_TEMPERATURE_MIN_INTERVAL}' to "
"'telemetry_request_min_interval' there"
)
raise cv.Invalid(message)
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
_LEGACY_SCHEMA = (
_BASE_SCHEMA.extend(uart.UART_DEVICE_SCHEMA)
.extend( .extend(
{ {
cv.Optional(CONF_UPDATE_INTERVAL, default="1s"): cv.update_interval, cv.Optional(CONF_CURRENT_TEMPERATURE_MIN_INTERVAL): cv.update_interval,
cv.Optional( cv.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
CONF_CURRENT_TEMPERATURE_MIN_INTERVAL, default="60s"
): cv.update_interval,
cv.Optional(
CONF_SUPPORTED_SWING_MODES, default="OFF"
): validate_climate_swing_mode,
} }
) )
.add_extra(_prepare_legacy_hub_config)
) )
FINAL_VALIDATE_SCHEMA = cv.All(
uart.final_validate_device_schema( @schema_extractor("schema")
"mitsubishi_cn105", def CONFIG_SCHEMA(config: ConfigType) -> ConfigType:
if config is SCHEMA_EXTRACT:
return _HUB_SCHEMA
if CONF_MITSUBISHI_CN105_ID in config or _has_top_level_hub_config():
return _HUB_SCHEMA(_validate_no_legacy_hub_keys(config))
return _LEGACY_SCHEMA(config)
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
def _legacy_final_validate(config: ConfigType) -> ConfigType:
if CONF_MITSUBISHI_CN105_ID in config:
return config
return uart.final_validate_device_schema(
DOMAIN,
require_rx=True, require_rx=True,
require_tx=True, require_tx=True,
data_bits=8, data_bits=8,
parity="EVEN", parity="EVEN",
stop_bits=1, stop_bits=1,
) )(config)
)
FINAL_VALIDATE_SCHEMA = _legacy_final_validate
async def to_code(config: ConfigType) -> None: async def to_code(config: ConfigType) -> None:
var = await climate.new_climate(config) var = await climate.new_climate(config)
await cg.register_component(var, config) climate_config = config.copy()
await uart.register_uart_device(var, config) # update_interval configures the protocol hub, not the climate entity.
cg.add(var.set_supported_swing_mode(config[CONF_SUPPORTED_SWING_MODES])) climate_config.pop(CONF_UPDATE_INTERVAL, None)
cg.add( await cg.register_component(var, climate_config)
var.set_current_temperature_min_interval( if CONF_MITSUBISHI_CN105_ID in config:
config[CONF_CURRENT_TEMPERATURE_MIN_INTERVAL] await register_mitsubishi_cn105_device(var, config)
) else:
) # Legacy climate-owned hub compatibility. Remove in 2027.2.0.
parent = cg.new_Pvariable(config[CONF_LEGACY_MITSUBISHI_CN105_ID])
await cg.register_component(parent, config)
@automation.register_action( await uart.register_uart_device(parent, config)
"climate.mitsubishi_cn105.set_remote_temperature", if CONF_CURRENT_TEMPERATURE_MIN_INTERVAL in config:
SetRemoteTemperatureAction, cg.add(
cv.Schema( parent.set_telemetry_request_min_interval(
{ config[CONF_CURRENT_TEMPERATURE_MIN_INTERVAL]
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Climate),
cv.Required(CONF_TEMPERATURE): cv.templatable(
cv.All(
cv.temperature,
cv.Range(min=8.0, max=39.5),
) )
), )
} cg.add(var.set_parent(parent))
), cg.add(var.set_supported_swing_mode(config[CONF_SUPPORTED_SWING_MODES]))
# Legacy climate action compatibility. Remove in 2027.2.0.
LEGACY_REMOTE_TEMPERATURE_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Climate),
cv.Required(CONF_TEMPERATURE): cv.templatable(
cv.All(
cv.temperature,
cv.Range(min=8.0, max=39.5),
)
),
}
)
# Legacy climate action compatibility. Remove in 2027.2.0.
LEGACY_CLEAR_REMOTE_TEMPERATURE_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Climate),
}
)
# Legacy climate action compatibility. Remove in 2027.2.0.
@automation.register_action(
f"climate.{DOMAIN}.set_remote_temperature",
LegacySetRemoteTemperatureAction,
LEGACY_REMOTE_TEMPERATURE_ACTION_SCHEMA,
synchronous=True, synchronous=True,
) )
async def set_remote_temperature_action_to_code( async def legacy_remote_temperature_action_to_code(
config: ConfigType, config: ConfigType,
action_id: ID, action_id: ID,
template_arg: cg.TemplateArguments, template_arg: cg.TemplateArguments,
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
_LOGGER.warning(
"The 'climate.%s.set_remote_temperature' action is deprecated. Use "
"'%s.set_remote_temperature' instead. It will be removed in ESPHome "
"2027.2.0.",
DOMAIN,
DOMAIN,
)
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
temperature = await cg.templatable(config[CONF_TEMPERATURE], args, float) temperature = await cg.templatable(config[CONF_TEMPERATURE], args, float)
cg.add(var.set_temperature(temperature)) cg.add(var.set_temperature(temperature))
return var return var
# Legacy climate action compatibility. Remove in 2027.2.0.
@automation.register_action( @automation.register_action(
"climate.mitsubishi_cn105.clear_remote_temperature", f"climate.{DOMAIN}.clear_remote_temperature",
ClearRemoteTemperatureAction, LegacyClearRemoteTemperatureAction,
cv.Schema( LEGACY_CLEAR_REMOTE_TEMPERATURE_ACTION_SCHEMA,
{
cv.Required(CONF_ID): cv.use_id(MitsubishiCN105Climate),
}
),
synchronous=True, synchronous=True,
) )
async def clear_remote_temperature_action_to_code( async def legacy_clear_temperature_action_to_code(
config: ConfigType, config: ConfigType,
action_id: ID, action_id: ID,
template_arg: cg.TemplateArguments, template_arg: cg.TemplateArguments,
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
_LOGGER.warning(
"The 'climate.%s.clear_remote_temperature' action is deprecated. Use "
"'%s.clear_remote_temperature' instead. It will be removed in ESPHome "
"2027.2.0.",
DOMAIN,
DOMAIN,
)
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
return var return var
@@ -1,8 +1,9 @@
#include "mitsubishi_cn105.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include "mitsubishi_cn105.h"
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
@@ -25,7 +26,7 @@ static constexpr std::array<uint8_t, 2> CONNECT_REQUEST_PAYLOAD = {0xCA, 0x01};
static constexpr uint8_t PACKET_TYPE_STATUS_REQUEST = 0x42; static constexpr uint8_t PACKET_TYPE_STATUS_REQUEST = 0x42;
static constexpr uint8_t PACKET_TYPE_STATUS_RESPONSE = 0x62; static constexpr uint8_t PACKET_TYPE_STATUS_RESPONSE = 0x62;
static constexpr uint8_t STATUS_MSG_SETTINGS = 0x02; static constexpr uint8_t STATUS_MSG_SETTINGS = 0x02;
static constexpr uint8_t STATUS_MSG_ROOM_TEMP = 0x03; static constexpr uint8_t STATUS_MSG_TELEMETRY = 0x03;
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41; static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41;
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61; static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61;
@@ -229,8 +230,8 @@ void MitsubishiCN105::did_transition_(State to) {
case State::STATUS_UPDATED: { case State::STATUS_UPDATED: {
if (this->pending_updates_.any() && this->is_status_initialized()) { if (this->pending_updates_.any() && this->is_status_initialized()) {
this->set_state_(State::APPLYING_SETTINGS); this->set_state_(State::APPLYING_SETTINGS);
} else if (this->current_status_msg_type_ == STATUS_MSG_SETTINGS && this->should_request_room_temperature_()) { } else if (this->current_status_msg_type_ == STATUS_MSG_SETTINGS && this->should_request_telemetry_()) {
this->current_status_msg_type_ = STATUS_MSG_ROOM_TEMP; this->current_status_msg_type_ = STATUS_MSG_TELEMETRY;
this->set_state_(State::UPDATING_STATUS); this->set_state_(State::UPDATING_STATUS);
} else { } else {
this->set_state_(State::SCHEDULE_NEXT_STATUS_UPDATE); this->set_state_(State::SCHEDULE_NEXT_STATUS_UPDATE);
@@ -264,16 +265,16 @@ void MitsubishiCN105::did_transition_(State to) {
} }
} }
bool MitsubishiCN105::should_request_room_temperature_() const { bool MitsubishiCN105::should_request_telemetry_() const {
if (!this->is_room_temperature_enabled()) { if (!this->is_telemetry_polling_enabled()) {
return false; return false;
} }
if (!this->last_room_temperature_update_ms_.has_value()) { if (!this->last_telemetry_update_ms_.has_value()) {
return true; return true;
} }
return (get_loop_time_ms() - *this->last_room_temperature_update_ms_) >= this->room_temperature_min_interval_ms_; return (get_loop_time_ms() - *this->last_telemetry_update_ms_) >= this->telemetry_request_min_interval_ms_;
} }
void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) { void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) {
@@ -327,7 +328,7 @@ bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len)
previous.fan_mode != this->status_.fan_mode || previous.target_temperature != this->status_.target_temperature || previous.fan_mode != this->status_.fan_mode || previous.target_temperature != this->status_.target_temperature ||
previous.vane_mode != this->status_.vane_mode || previous.wide_vane_mode != this->status_.wide_vane_mode; previous.vane_mode != this->status_.vane_mode || previous.wide_vane_mode != this->status_.wide_vane_mode;
if (this->is_room_temperature_enabled()) { if (this->is_telemetry_polling_enabled()) {
changed |= previous.room_temperature != this->status_.room_temperature; changed |= previous.room_temperature != this->status_.room_temperature;
} }
@@ -339,8 +340,8 @@ bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *pay
case STATUS_MSG_SETTINGS: case STATUS_MSG_SETTINGS:
return this->parse_status_settings_(payload, len); return this->parse_status_settings_(payload, len);
case STATUS_MSG_ROOM_TEMP: case STATUS_MSG_TELEMETRY:
return this->parse_status_room_temperature_(payload, len); return this->parse_status_telemetry_(payload, len);
default: default:
ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type); ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type);
@@ -384,14 +385,14 @@ bool MitsubishiCN105::parse_status_settings_(const uint8_t *payload, size_t len)
return true; return true;
} }
bool MitsubishiCN105::parse_status_room_temperature_(const uint8_t *payload, size_t len) { bool MitsubishiCN105::parse_status_telemetry_(const uint8_t *payload, size_t len) {
if (len <= 5) { if (len <= 5) {
ESP_LOGVV(TAG, "RX room temperature payload too short"); ESP_LOGVV(TAG, "RX telemetry payload too short");
return false; return false;
} }
this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10); this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10);
this->last_room_temperature_update_ms_ = get_loop_time_ms(); this->last_telemetry_update_ms_ = get_loop_time_ms();
return true; return true;
} }
@@ -1,9 +1,10 @@
#pragma once #pragma once
#include "esphome/components/uart/uart.h"
#include "esphome/core/finite_set_mask.h"
#include <cmath> #include <cmath>
#include <optional> #include <optional>
#include "esphome/components/uart/uart.h"
#include "esphome/core/finite_set_mask.h"
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
@@ -70,16 +71,16 @@ class MitsubishiCN105 {
uint32_t get_update_interval() const { return this->update_interval_ms_; } uint32_t get_update_interval() const { return this->update_interval_ms_; }
void set_update_interval(uint32_t interval_ms) { this->update_interval_ms_ = interval_ms; } void set_update_interval(uint32_t interval_ms) { this->update_interval_ms_ = interval_ms; }
uint32_t get_room_temperature_min_interval() const { return this->room_temperature_min_interval_ms_; } uint32_t get_telemetry_request_min_interval() const { return this->telemetry_request_min_interval_ms_; }
bool is_room_temperature_enabled() const { return this->room_temperature_min_interval_ms_ != SCHEDULER_DONT_RUN; } bool is_telemetry_polling_enabled() const { return this->telemetry_request_min_interval_ms_ != SCHEDULER_DONT_RUN; }
void set_room_temperature_min_interval(uint32_t interval_ms) { void set_telemetry_request_min_interval(uint32_t interval_ms) {
this->room_temperature_min_interval_ms_ = interval_ms; this->telemetry_request_min_interval_ms_ = interval_ms;
} }
const Status &status() const { return this->status_; } const Status &status() const { return this->status_; }
bool is_status_initialized() const { bool is_status_initialized() const {
return this->is_room_temperature_enabled() ? !std::isnan(this->status_.room_temperature) return this->is_telemetry_polling_enabled() ? !std::isnan(this->status_.room_temperature)
: !std::isnan(this->status_.target_temperature); : !std::isnan(this->status_.target_temperature);
} }
void set_power(bool power_on); void set_power(bool power_on);
@@ -150,10 +151,10 @@ class MitsubishiCN105 {
bool process_status_packet_(const uint8_t *payload, size_t len); bool process_status_packet_(const uint8_t *payload, size_t len);
bool parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len); bool parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len);
bool parse_status_settings_(const uint8_t *payload, size_t len); bool parse_status_settings_(const uint8_t *payload, size_t len);
bool parse_status_room_temperature_(const uint8_t *payload, size_t len); bool parse_status_telemetry_(const uint8_t *payload, size_t len);
void send_packet_(const uint8_t *packet, size_t len); void send_packet_(const uint8_t *packet, size_t len);
void update_status_(); void update_status_();
bool should_request_room_temperature_() const; bool should_request_telemetry_() const;
void apply_settings_(); void apply_settings_();
bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); } bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); }
void set_remote_temperature_half_deg_(uint8_t temperature_half_deg); void set_remote_temperature_half_deg_(uint8_t temperature_half_deg);
@@ -162,11 +163,15 @@ class MitsubishiCN105 {
static const LogString *state_to_string(State state); static const LogString *state_to_string(State state);
uart::UARTDevice &device_; uart::UARTDevice &device_;
// Default 1s; legacy climate-owned hub compatibility relies on this when update_interval is omitted.
// Remove legacy note in 2027.2.0.
uint32_t update_interval_ms_{1000}; uint32_t update_interval_ms_{1000};
uint32_t status_update_wait_credit_ms_{0}; uint32_t status_update_wait_credit_ms_{0};
uint32_t operation_start_ms_{0}; uint32_t operation_start_ms_{0};
uint32_t room_temperature_min_interval_ms_{60000}; // Default 60s; legacy climate-owned hub compatibility relies on this when current_temperature_min_interval is
std::optional<uint32_t> last_room_temperature_update_ms_; // omitted. Remove legacy note in 2027.2.0.
uint32_t telemetry_request_min_interval_ms_{60000};
std::optional<uint32_t> last_telemetry_update_ms_;
Status status_{}; Status status_{};
State state_{State::NOT_CONNECTED}; State state_{State::NOT_CONNECTED};
UpdateFlags pending_updates_; UpdateFlags pending_updates_;
@@ -1,5 +1,5 @@
#include <cinttypes>
#include "mitsubishi_cn105_climate.h" #include "mitsubishi_cn105_climate.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
@@ -50,25 +50,11 @@ static constexpr std::optional<Left> reverse_map_lookup(const std::array<std::pa
return key.has_value() ? reverse_map_lookup(map, *key) : std::nullopt; return key.has_value() ? reverse_map_lookup(map, *key) : std::nullopt;
} }
void MitsubishiCN105Climate::dump_config() { void MitsubishiCN105Climate::dump_config() { LOG_CLIMATE("", "Mitsubishi CN105 Climate", this); }
LOG_CLIMATE("", "Mitsubishi CN105 Climate", this);
if (this->hp_.is_room_temperature_enabled()) {
ESP_LOGCONFIG(TAG, " Current temperature min interval: %" PRIu32 " ms",
this->hp_.get_room_temperature_min_interval());
} else {
ESP_LOGCONFIG(TAG, " Current temperature: DISABLED");
}
ESP_LOGCONFIG(TAG,
" Update interval: %" PRIu32 " ms\n"
" UART: baud_rate=%" PRIu32 " data_bits=%u parity=%s stop_bits=%u",
this->hp_.get_update_interval(), this->parent_->get_baud_rate(), this->parent_->get_data_bits(),
LOG_STR_ARG(parity_to_str(this->parent_->get_parity())), this->parent_->get_stop_bits());
}
void MitsubishiCN105Climate::setup() { this->hp_.initialize(); } void MitsubishiCN105Climate::setup() {
this->parent_->add_on_status_callback([this]() { this->apply_values_(); });
void MitsubishiCN105Climate::loop() { if (this->parent_->is_status_initialized()) {
if (this->hp_.update()) {
this->apply_values_(); this->apply_values_();
} }
} }
@@ -90,7 +76,7 @@ climate::ClimateTraits MitsubishiCN105Climate::traits() {
traits.set_visual_max_temperature(31.0f); traits.set_visual_max_temperature(31.0f);
traits.set_visual_temperature_step(1.0f); traits.set_visual_temperature_step(1.0f);
if (this->hp_.is_room_temperature_enabled()) { if (this->parent_->is_telemetry_polling_enabled()) {
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE); traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
traits.set_visual_current_temperature_step(0.5f); traits.set_visual_current_temperature_step(0.5f);
} }
@@ -100,20 +86,20 @@ climate::ClimateTraits MitsubishiCN105Climate::traits() {
void MitsubishiCN105Climate::control(const climate::ClimateCall &call) { void MitsubishiCN105Climate::control(const climate::ClimateCall &call) {
if (const auto target_temperature = call.get_target_temperature()) { if (const auto target_temperature = call.get_target_temperature()) {
this->hp_.set_target_temperature(*target_temperature); this->parent_->set_target_temperature(*target_temperature);
} }
if (const auto mode = call.get_mode()) { if (const auto mode = call.get_mode()) {
if (*mode == climate::CLIMATE_MODE_OFF) { if (*mode == climate::CLIMATE_MODE_OFF) {
this->hp_.set_power(false); this->parent_->set_power(false);
} else if (const auto mapped = reverse_map_lookup(MODE_MAP, *mode)) { } else if (const auto mapped = reverse_map_lookup(MODE_MAP, *mode)) {
this->hp_.set_power(true); this->parent_->set_power(true);
this->hp_.set_mode(*mapped); this->parent_->set_mode(*mapped);
} }
} }
if (const auto fan_mode = reverse_map_lookup(FAN_MODE_MAP, call.get_fan_mode())) { if (const auto fan_mode = reverse_map_lookup(FAN_MODE_MAP, call.get_fan_mode())) {
this->hp_.set_fan_mode(*fan_mode); this->parent_->set_fan_mode(*fan_mode);
} }
if (const auto swing_mode = call.get_swing_mode()) { if (const auto swing_mode = call.get_swing_mode()) {
@@ -140,24 +126,24 @@ void MitsubishiCN105Climate::control(const climate::ClimateCall &call) {
} }
if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) { if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) {
this->hp_.set_vane_mode(vane); this->parent_->set_vane_mode(vane);
} }
if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) { if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) {
this->hp_.set_wide_vane_mode(wide); this->parent_->set_wide_vane_mode(wide);
} }
} }
if (this->hp_.is_status_initialized()) { if (this->parent_->is_status_initialized()) {
this->apply_values_(); this->apply_values_();
} }
} }
void MitsubishiCN105Climate::apply_values_() { void MitsubishiCN105Climate::apply_values_() {
const auto &status = this->hp_.status(); const auto &status = this->parent_->status();
this->target_temperature = status.target_temperature; this->target_temperature = status.target_temperature;
if (this->hp_.is_room_temperature_enabled()) { if (this->parent_->is_telemetry_polling_enabled()) {
this->current_temperature = status.room_temperature; this->current_temperature = status.room_temperature;
} }
@@ -1,51 +1,47 @@
#pragma once #pragma once
#include "mitsubishi_cn105_component.h"
#include "mitsubishi_cn105.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/climate/climate.h" #include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart.h"
#include "mitsubishi_cn105.h"
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
class MitsubishiCN105Climate : public climate::Climate, public Component, public uart::UARTDevice { class MitsubishiCN105Climate : public climate::Climate, public Component, public Parented<MitsubishiCN105Component> {
public: public:
explicit MitsubishiCN105Climate() : hp_(*this) {}
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
climate::ClimateTraits traits() override; climate::ClimateTraits traits() override;
void control(const climate::ClimateCall &call) override; void control(const climate::ClimateCall &call) override;
void set_update_interval(uint32_t ms) { this->hp_.set_update_interval(ms); }
void set_current_temperature_min_interval(uint32_t ms) { this->hp_.set_room_temperature_min_interval(ms); }
void set_remote_temperature(float temperature) { this->hp_.set_remote_temperature(temperature); }
void clear_remote_temperature() { this->hp_.clear_remote_temperature(); }
void set_supported_swing_mode(climate::ClimateSwingMode mode); void set_supported_swing_mode(climate::ClimateSwingMode mode);
// Legacy climate action compatibility. Remove in 2027.2.0.
void set_remote_temperature(float temperature) { this->parent_->set_remote_temperature(temperature); }
void clear_remote_temperature() { this->parent_->clear_remote_temperature(); }
protected: protected:
void apply_values_(); void apply_values_();
MitsubishiCN105 hp_;
climate::ClimateSwingModeMask supported_swing_modes_{}; climate::ClimateSwingModeMask supported_swing_modes_{};
MitsubishiCN105::VaneMode last_non_swing_vane_mode_{MitsubishiCN105::VaneMode::AUTO}; MitsubishiCN105::VaneMode last_non_swing_vane_mode_{MitsubishiCN105::VaneMode::AUTO};
MitsubishiCN105::WideVaneMode last_non_swing_wide_vane_mode_{MitsubishiCN105::WideVaneMode::CENTER}; MitsubishiCN105::WideVaneMode last_non_swing_wide_vane_mode_{MitsubishiCN105::WideVaneMode::CENTER};
}; };
// Legacy climate action compatibility. Remove in 2027.2.0.
template<typename... Ts> template<typename... Ts>
class SetRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Climate> { class LegacySetRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Climate> {
public: public:
TEMPLATABLE_VALUE(float, temperature) TEMPLATABLE_VALUE(float, temperature)
void play(const Ts &...x) override { this->parent_->set_remote_temperature(this->temperature_.value(x...)); } void play(const Ts &...x) override { this->parent_->set_remote_temperature(this->temperature_.value(x...)); }
}; };
// Legacy climate action compatibility. Remove in 2027.2.0.
template<typename... Ts> template<typename... Ts>
class ClearRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Climate> { class LegacyClearRemoteTemperatureAction : public Action<Ts...>, public Parented<MitsubishiCN105Climate> {
public: public:
void play(const Ts &...x) override { this->parent_->clear_remote_temperature(); } void play(const Ts &...x) override { this->parent_->clear_remote_temperature(); }
}; };
@@ -0,0 +1,34 @@
#include "mitsubishi_cn105_component.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome::mitsubishi_cn105 {
static const char *const TAG = "mitsubishi_cn105";
void MitsubishiCN105Component::dump_config() {
ESP_LOGCONFIG(TAG, "Mitsubishi CN105:");
if (this->hp_.is_telemetry_polling_enabled()) {
ESP_LOGCONFIG(TAG, " Telemetry polling min interval: %" PRIu32 " ms",
this->hp_.get_telemetry_request_min_interval());
} else {
ESP_LOGCONFIG(TAG, " Telemetry polling: DISABLED");
}
ESP_LOGCONFIG(TAG,
" Update interval: %" PRIu32 " ms\n"
" UART: baud_rate=%" PRIu32 " data_bits=%u parity=%s stop_bits=%u",
this->hp_.get_update_interval(), this->parent_->get_baud_rate(), this->parent_->get_data_bits(),
LOG_STR_ARG(parity_to_str(this->parent_->get_parity())), this->parent_->get_stop_bits());
}
void MitsubishiCN105Component::setup() { this->hp_.initialize(); }
void MitsubishiCN105Component::loop() {
if (this->hp_.update()) {
this->status_callback_.call();
}
}
} // namespace esphome::mitsubishi_cn105
@@ -0,0 +1,46 @@
#pragma once
#include "mitsubishi_cn105.h"
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include <utility>
namespace esphome::mitsubishi_cn105 {
class MitsubishiCN105Component : public Component, public uart::UARTDevice {
public:
explicit MitsubishiCN105Component() : hp_(*this) {}
void setup() override;
void loop() override;
void dump_config() override;
void set_update_interval(uint32_t ms) { this->hp_.set_update_interval(ms); }
void set_telemetry_request_min_interval(uint32_t ms) { this->hp_.set_telemetry_request_min_interval(ms); }
void set_remote_temperature(float temperature) { this->hp_.set_remote_temperature(temperature); }
void clear_remote_temperature() { this->hp_.clear_remote_temperature(); }
void set_power(bool power_on) { this->hp_.set_power(power_on); }
void set_target_temperature(float target_temperature) { this->hp_.set_target_temperature(target_temperature); }
void set_mode(MitsubishiCN105::Mode mode) { this->hp_.set_mode(mode); }
void set_fan_mode(MitsubishiCN105::FanMode fan_mode) { this->hp_.set_fan_mode(fan_mode); }
void set_vane_mode(MitsubishiCN105::VaneMode vane_mode) { this->hp_.set_vane_mode(vane_mode); }
void set_wide_vane_mode(MitsubishiCN105::WideVaneMode mode) { this->hp_.set_wide_vane_mode(mode); }
const MitsubishiCN105::Status &status() const { return this->hp_.status(); }
bool is_status_initialized() const { return this->hp_.is_status_initialized(); }
bool is_telemetry_polling_enabled() const { return this->hp_.is_telemetry_polling_enabled(); }
template<typename F> void add_on_status_callback(F &&callback) {
this->status_callback_.add(std::forward<F>(callback));
}
protected:
MitsubishiCN105 hp_;
CallbackManager<void()> status_callback_;
};
} // namespace esphome::mitsubishi_cn105
@@ -0,0 +1,10 @@
mitsubishi_cn105:
id: ac_hub
climate:
- platform: mitsubishi_cn105
mitsubishi_cn105_id: ac_hub
name: AC
current_temperature_min_interval: 30s
uart_id: uart_bus
update_interval: 10s
@@ -0,0 +1,30 @@
"""Tests for Mitsubishi CN105 climate configuration migration diagnostics."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components.mitsubishi_cn105 import climate
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.yaml_util import load_yaml
def test_top_level_hub_rejects_leftover_legacy_climate_keys(
component_fixture_path: Callable[[str], Path],
) -> None:
config = load_yaml(
component_fixture_path("top_level_hub_with_legacy_climate_keys.yaml")
)
CORE.raw_config = config
with pytest.raises(cv.Invalid) as exc_info:
climate.CONFIG_SCHEMA(config["climate"][0])
message = str(exc_info.value)
assert "'current_temperature_min_interval'" in message
assert "'uart_id'" in message
assert "'update_interval'" in message
assert "top-level 'mitsubishi_cn105:' block" in message
assert "'telemetry_request_min_interval'" in message
@@ -75,7 +75,7 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::POSITION_4); EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::POSITION_4);
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::SWING); EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::SWING);
// Now fetch room temperature (0x03) // Now fetch telemetry (0x03)
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS); EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A)); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A));
@@ -84,11 +84,11 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
// Clear TX bytes. // Clear TX bytes.
ctx.uart.tx.clear(); ctx.uart.tx.clear();
// Room temperature response // Telemetry response
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x0B, 0x00, 0x00, ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x0B, 0x00, 0x00,
0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5}); 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5});
// Room temperature should still have initial value // Room temperature from telemetry should still have initial value
EXPECT_THAT(ctx.sut.status().room_temperature, ::testing::IsNan()); EXPECT_THAT(ctx.sut.status().room_temperature, ::testing::IsNan());
ctx.sut.set_current_time(400); ctx.sut.set_current_time(400);
@@ -97,7 +97,7 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
EXPECT_TRUE(ctx.uart.rx.empty()); EXPECT_TRUE(ctx.uart.rx.empty());
EXPECT_TRUE(ctx.sut.is_status_initialized()); EXPECT_TRUE(ctx.sut.is_status_initialized());
// Check room temperature we just read from received package // Check room temperature we just read from telemetry package
EXPECT_EQ(ctx.sut.status().room_temperature, 21.0f); EXPECT_EQ(ctx.sut.status().room_temperature, 21.0f);
EXPECT_TRUE(ctx.uart.tx.empty()); EXPECT_TRUE(ctx.uart.tx.empty());
+7 -1
View File
@@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include "esphome/components/uart/uart_component.h" #include "esphome/components/uart/uart_component.h"
#include "esphome/components/mitsubishi_cn105/mitsubishi_cn105.h" #include "esphome/components/mitsubishi_cn105/mitsubishi_cn105.h"
#include "esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h"
#include "esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h" #include "esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h"
namespace esphome::mitsubishi_cn105::testing { namespace esphome::mitsubishi_cn105::testing {
@@ -65,11 +66,16 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 {
class TestableMitsubishiCN105Climate : public MitsubishiCN105Climate { class TestableMitsubishiCN105Climate : public MitsubishiCN105Climate {
public: public:
TestableMitsubishiCN105Climate() { this->set_parent(&this->component_); }
using MitsubishiCN105Climate::apply_values_; using MitsubishiCN105Climate::apply_values_;
using MitsubishiCN105Climate::last_non_swing_vane_mode_; using MitsubishiCN105Climate::last_non_swing_vane_mode_;
using MitsubishiCN105Climate::last_non_swing_wide_vane_mode_; using MitsubishiCN105Climate::last_non_swing_wide_vane_mode_;
MitsubishiCN105::Status &status() { return static_cast<TestableMitsubishiCN105 &>(this->hp_).status_; } MitsubishiCN105::Status &status() { return const_cast<MitsubishiCN105::Status &>(this->component_.status()); }
protected:
MitsubishiCN105Component component_;
}; };
} // namespace esphome::mitsubishi_cn105::testing } // namespace esphome::mitsubishi_cn105::testing
@@ -1,17 +1,20 @@
mitsubishi_cn105:
id: ac
uart_id: uart_bus
update_interval: 30s
telemetry_request_min_interval: 120s
climate: climate:
- platform: mitsubishi_cn105 - platform: mitsubishi_cn105
id: ac mitsubishi_cn105_id: ac
name: "AC Test" name: "AC Test"
uart_id: uart_bus
update_interval: 30s
current_temperature_min_interval: 120s
supported_swing_modes: BOTH supported_swing_modes: BOTH
esphome: esphome:
on_boot: on_boot:
then: then:
- climate.mitsubishi_cn105.set_remote_temperature: - mitsubishi_cn105.set_remote_temperature:
id: ac id: ac
temperature: 22.0 temperature: 22.0
- climate.mitsubishi_cn105.clear_remote_temperature: - mitsubishi_cn105.clear_remote_temperature:
id: ac id: ac
@@ -0,0 +1,16 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
climate:
- platform: mitsubishi_cn105
id: ac
name: "AC Test"
esphome:
on_boot:
then:
- climate.mitsubishi_cn105.set_remote_temperature:
id: ac
temperature: 22.0
- climate.mitsubishi_cn105.clear_remote_temperature:
id: ac
@@ -0,0 +1,7 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
climate:
- platform: mitsubishi_cn105
name: "AC Test"
current_temperature_min_interval: 30s
@@ -0,0 +1,6 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
climate:
- platform: mitsubishi_cn105
name: "AC Test"
@@ -0,0 +1,7 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
climate:
- platform: mitsubishi_cn105
name: "AC Test"
uart_id: uart_bus
@@ -0,0 +1,7 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
climate:
- platform: mitsubishi_cn105
name: "AC Test"
update_interval: 30s
@@ -0,0 +1,8 @@
packages:
uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-idf.yaml
mitsubishi_cn105:
climate:
- platform: mitsubishi_cn105
name: "AC Test"