mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[mitsubishi_cn105] Extract top-level hub (#16987)
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
+10
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
+7
@@ -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
|
||||||
+7
@@ -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"
|
||||||
Reference in New Issue
Block a user