Files
esphome_aux_ac_component/components/aux_ac/climate.py

334 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import climate, uart, sensor, binary_sensor
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.const import (
CONF_ID,
CONF_UART_ID,
CONF_PERIOD,
CONF_CUSTOM_FAN_MODES,
CONF_CUSTOM_PRESETS,
CONF_INTERNAL,
CONF_DATA,
CONF_SUPPORTED_MODES,
CONF_SUPPORTED_SWING_MODES,
CONF_SUPPORTED_PRESETS,
CONF_PRESSURE,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_PASCAL,
ICON_POWER,
ICON_THERMOMETER,
ICON_GAS_CYLINDER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
)
from esphome.components.climate import (
ClimateMode,
ClimatePreset,
ClimateSwingMode,
)
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@GrKoR"]
DEPENDENCIES = ["climate", "uart"]
AUTO_LOAD = ["sensor", "binary_sensor"]
CONF_SHOW_ACTION = 'show_action'
CONF_INDOOR_TEMPERATURE = 'indoor_temperature'
CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature'
ICON_OUTDOOR_TEMPERATURE = 'mdi:home-thermometer-outline'
CONF_INBOUND_TEMPERATURE = 'inbound_temperature'
ICON_INBOUND_TEMPERATURE = 'mdi:thermometer-plus'
CONF_OUTBOUND_TEMPERATURE = 'outbound_temperature'
ICON_OUTBOUND_TEMPERATURE = 'mdi:thermometer-minus'
CONF_STRANGE_TEMPERATURE = 'strange_temperature'
ICON_STRANGE_TEMPERATURE = 'mdi:thermometer-lines'
CONF_DISPLAY_STATE = 'display_state'
CONF_INVERTOR_POWER = 'invertor_power'
CONF_DEFROST_STATE = 'defrost_state'
ICON_DEFROST = "mdi:snowflake-melt"
CONF_DISPLAY_INVERTED = 'display_inverted'
ICON_DISPLAY = "mdi:clock-digital"
aux_ac_ns = cg.esphome_ns.namespace("aux_ac")
AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component)
Capabilities = aux_ac_ns.namespace("Constants")
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action)
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action)
AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action)
ALLOWED_CLIMATE_MODES = {
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
}
validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True)
ALLOWED_CLIMATE_PRESETS = {
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
}
validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True)
ALLOWED_CLIMATE_SWING_MODES = {
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
}
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
CUSTOM_FAN_MODES = {
"MUTE": Capabilities.MUTE,
"TURBO": Capabilities.TURBO,
}
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
CUSTOM_PRESETS = {
"CLEAN": Capabilities.CLEAN,
"FEEL": Capabilities.FEEL,
"HEALTH": Capabilities.HEALTH,
"ANTIFUNGUS": Capabilities.ANTIFUNGUS,
}
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
def validate_raw_data(value):
if isinstance(value, list):
return cv.Schema([cv.hex_uint8_t])(value)
raise cv.Invalid(
"data must be a list of bytes"
)
def output_info(config):
"""_LOGGER.info(config)"""
return config
CONFIG_SCHEMA = cv.All(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirCon),
cv.Optional(CONF_PERIOD, default="7s"): cv.time_period,
cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean,
cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean,
cv.Optional(CONF_DEFROST_STATE, default="false"): cv.boolean,
cv.Optional(CONF_INVERTOR_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_POWER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_OUTDOOR_TEMPERATURE,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_INBOUND_TEMPERATURE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_OUTBOUND_TEMPERATURE,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_STRANGE_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_STRANGE_TEMPERATURE,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema(
icon=ICON_DISPLAY
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean
}
),
cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema(
icon=ICON_DEFROST
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean
}
),
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes),
cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(validate_swing_modes),
cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets),
cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets),
cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(validate_custom_fan_modes),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
output_info
)
async def to_code(config):
"""_LOGGER.info("--------------")"""
"""_LOGGER.info(config)"""
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
parent = await cg.get_variable(config[CONF_UART_ID])
cg.add(var.initAC(parent))
if CONF_INDOOR_TEMPERATURE in config:
conf = config[CONF_INDOOR_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_indoor_temperature_sensor(sens))
if CONF_OUTDOOR_TEMPERATURE in config:
conf = config[CONF_OUTDOOR_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_outdoor_temperature_sensor(sens))
if CONF_OUTBOUND_TEMPERATURE in config:
conf = config[CONF_OUTBOUND_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_outbound_temperature_sensor(sens))
if CONF_INBOUND_TEMPERATURE in config:
conf = config[CONF_INBOUND_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_inbound_temperature_sensor(sens))
if CONF_STRANGE_TEMPERATURE in config:
conf = config[CONF_STRANGE_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_strange_temperature_sensor(sens))
if CONF_DISPLAY_STATE in config:
conf = config[CONF_DISPLAY_STATE]
sens = await binary_sensor.new_binary_sensor(conf)
cg.add(var.set_display_sensor(sens))
if CONF_DEFROST_STATE in config:
conf = config[CONF_DEFROST_STATE]
sens = await binary_sensor.new_binary_sensor(conf)
cg.add(var.set_defrost_state(sens))
if CONF_INVERTOR_POWER in config:
conf = config[CONF_INVERTOR_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_invertor_power_sensor(sens))
cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_CUSTOM_PRESETS in config:
cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
if CONF_CUSTOM_FAN_MODES in config:
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
DISPLAY_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(AirCon),
}
)
@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA)
async def display_off_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA)
async def display_on_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(AirCon),
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
}
)
# *********************************************************************************************************
# ВАЖНО! Только для инженеров!
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
# Вы действуете на свой страх и риск.
# *********************************************************************************************************
@automation.register_action(
"aux_ac.send_packet",
AirConSendTestPacketAction,
SEND_TEST_PACKET_ACTION_SCHEMA
)
async def send_packet_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
data = config[CONF_DATA]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
cg.add(var.set_data_static(data))
return var