Files
esphome/esphome/components/remote_base/__init__.py
T

2732 lines
71 KiB
Python

from collections.abc import Callable
from pathlib import Path
from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.config_helpers import filter_source_files_from_defines
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_BATTERY_LEVEL,
CONF_BUTTON,
CONF_CARRIER_FREQUENCY,
CONF_CHANNEL,
CONF_CHECK,
CONF_CODE,
CONF_COMMAND,
CONF_COMMAND_REPEATS,
CONF_DATA,
CONF_DELTA,
CONF_DEVICE,
CONF_FAMILY,
CONF_FORCE_UPDATE,
CONF_GROUP,
CONF_HUMIDITY,
CONF_ID,
CONF_INDEX,
CONF_INVERTED,
CONF_KEY,
CONF_LEVEL,
CONF_MAGNITUDE,
CONF_NBITS,
CONF_ONE,
CONF_PROTOCOL,
CONF_PULSE_LENGTH,
CONF_RC_CODE_1,
CONF_RC_CODE_2,
CONF_REPEAT,
CONF_SECOND,
CONF_SOURCE,
CONF_STATE,
CONF_SYNC,
CONF_TEMPERATURE,
CONF_TIMES,
CONF_TRIGGER_ID,
CONF_TYPE_ID,
CONF_WAIT_TIME,
CONF_WAND_ID,
CONF_ZERO,
)
from esphome.core import ID, coroutine
from esphome.cpp_generator import MockObj
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType, SafeExpType, TemplateArgsType
from esphome.util import Registry, SimpleRegistry
AUTO_LOAD = ["binary_sensor"]
DOMAIN = "remote_base"
CONF_ADDRESS_2 = "address_2"
CONF_RECEIVER_ID = "receiver_id"
CONF_TRANSMITTER_ID = "transmitter_id"
CONF_FIRST = "first"
CONF_SUFFIX = "suffix"
CONF_SUFFIX_BITS = "suffix_bits"
CONF_CONTROL = "control"
CONF_CONTROL_IN_KEY = "control_in_key"
CONF_ROLLING_CODE = "rolling_code"
CONF_EXTENSION = "extension"
ns = remote_base_ns = cg.esphome_ns.namespace("remote_base")
RemoteProtocol = ns.class_("RemoteProtocol")
RemoteReceiverListener = ns.class_("RemoteReceiverListener")
RemoteReceiverBinarySensorBase = ns.class_(
"RemoteReceiverBinarySensorBase", binary_sensor.BinarySensor, cg.Component
)
RemoteReceiverTrigger = ns.class_(
"RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener
)
RemoteReceiverDumperBase = ns.class_("RemoteReceiverDumperBase")
RemoteTransmittable = ns.class_("RemoteTransmittable")
RemoteTransmitterActionBase = ns.class_(
"RemoteTransmitterActionBase", RemoteTransmittable, automation.Action
)
RemoteReceiverBase = ns.class_("RemoteReceiverBase")
RemoteTransmitterBase = ns.class_("RemoteTransmitterBase")
def templatize(value):
if isinstance(value, cv.Schema):
value = value.schema
ret = {}
for key, val in value.items():
ret[key] = cv.templatable(val)
return cv.Schema(ret)
REMOTE_LISTENER_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase),
}
)
REMOTE_TRANSMITTABLE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase),
}
)
# Listener and dumper lists are StaticVectors sized from these counts, so every registration
# must go through add_listener / add_dumper. Every receiver's list gets the same capacity, so
# the slots are keyed by receiver and the define is the largest count any one receiver needs.
LISTENER_COUNT_DEFINE = "REMOTE_BASE_LISTENER_COUNT"
DUMPER_COUNT_DEFINE = "REMOTE_BASE_DUMPER_COUNT"
_request_listener_slot = cg.slot_counter(LISTENER_COUNT_DEFINE)
_request_dumper_slot = cg.slot_counter(DUMPER_COUNT_DEFINE)
def add_listener(receiver: MockObj, listener: MockObj) -> None:
_request_listener_slot(str(receiver))
cg.add(receiver.register_listener(listener))
def add_dumper(receiver: MockObj, dumper: MockObj) -> None:
_request_dumper_slot(str(receiver))
cg.add(receiver.register_dumper(dumper))
async def register_listener(var: MockObj, config: ConfigType) -> None:
receiver = await cg.get_variable(config[CONF_RECEIVER_ID])
add_listener(receiver, var)
async def attach_receiver(
var: MockObj, config: ConfigType, key: str = CONF_RECEIVER_ID
) -> None:
"""Link the configured receiver to an entity and register the entity as its listener.
The C++ set_receiver() no longer registers the listener; the slot for it is counted here.
"""
receiver = await cg.get_variable(config[key])
cg.add(var.set_receiver(receiver))
add_listener(receiver, var)
async def register_transmittable(var, config, key: str = CONF_TRANSMITTER_ID):
transmitter_ = await cg.get_variable(config[key])
cg.add(var.set_transmitter(transmitter_))
# Registry names that share a protocol source file
def _protocol_stem(name: str) -> str:
if name.startswith("rc_switch"):
return "rc_switch"
if name == "canalsatld":
return "canalsat"
return name
def protocol_define(name: str) -> str:
return f"USE_REMOTE_PROTOCOL_{_protocol_stem(name).upper()}"
_PROTOCOL_STEMS = sorted(
path.name.removesuffix("_protocol.cpp")
for path in Path(__file__).parent.glob("*_protocol.cpp")
)
def request_protocol(name: str) -> None:
"""Keep a protocol's source file in the build; components using it from C++ must call this."""
if _protocol_stem(name) not in _PROTOCOL_STEMS:
raise ValueError(
f"Unknown remote protocol {name!r}; expected one of {', '.join(_PROTOCOL_STEMS)}"
)
cg.add_define(protocol_define(name))
def _request_protocol_if_in_tree(name: str) -> None:
"""Registry names from external components have no source file here and need no define."""
if _protocol_stem(name) in _PROTOCOL_STEMS:
request_protocol(name)
# Only the protocol sources a configuration uses are compiled
FILTER_SOURCE_FILES = filter_source_files_from_defines(
{f"{stem}_protocol.cpp": protocol_define(stem) for stem in _PROTOCOL_STEMS}
)
def register_binary_sensor(
name: str, type: MockObj, schema: cv.Schema | dict
) -> Callable[[Callable[[MockObj, ConfigType], Any]], Callable]:
registerer = BINARY_SENSOR_REGISTRY.register(name, type, schema)
def decorator(func: Callable[[MockObj, ConfigType], Any]) -> Callable:
async def new_func(var: MockObj, config: ConfigType) -> None:
_request_protocol_if_in_tree(name)
await coroutine(func)(var, config)
return registerer(new_func)
return decorator
def register_trigger(name, type, data_type):
validator = automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type),
}
)
registerer = TRIGGER_REGISTRY.register(f"on_{name}", validator)
def decorator(func):
async def new_func(config):
_request_protocol_if_in_tree(name)
var = cg.new_Pvariable(config[CONF_TRIGGER_ID])
await coroutine(func)(var, config)
await automation.build_automation(var, [(data_type, "x")], config)
return var
return registerer(new_func)
return decorator
def register_dumper(name, type, schema=None):
if schema is None:
schema = {}
registerer = DUMPER_REGISTRY.register(name, type, schema)
def decorator(func):
async def new_func(config, dumper_id):
_request_protocol_if_in_tree(name)
var = cg.new_Pvariable(dumper_id)
await coroutine(func)(var, config)
return var
return registerer(new_func)
return decorator
def validate_repeat(value):
if isinstance(value, dict):
return cv.Schema(
{
cv.Required(CONF_TIMES): cv.templatable(cv.positive_int),
cv.Optional(CONF_WAIT_TIME, default="25ms"): cv.templatable(
cv.positive_time_period_microseconds
),
}
)(value)
return validate_repeat({CONF_TIMES: value})
BASE_REMOTE_TRANSMITTER_SCHEMA = cv.Schema(
{
cv.Optional(CONF_REPEAT): validate_repeat,
}
).extend(REMOTE_TRANSMITTABLE_SCHEMA)
def _transmit_action(
name: str,
schema: cv.Schema,
validate: Callable[[ConfigType], ConfigType] | None,
) -> tuple[str, cv.Schema, Any]:
"""The action name, its schema and the schema with ``validate`` applied."""
schema = templatize(schema).extend(BASE_REMOTE_TRANSMITTER_SCHEMA)
validator = schema if validate is None else cv.All(schema, validate)
return f"remote_transmitter.transmit_{name}", schema, validator
def register_action(
name, type_, schema, *, validate: Callable[[ConfigType], ConfigType] | None = None
):
action, _, validator = _transmit_action(name, schema, validate)
registerer = automation.register_action(
action,
type_,
validator,
synchronous=True,
)
def decorator(func):
async def new_func(config, action_id, template_arg, args):
_request_protocol_if_in_tree(name)
var = cg.new_Pvariable(action_id, template_arg)
await register_transmittable(var, config)
if CONF_REPEAT in config:
conf = config[CONF_REPEAT]
template_ = await cg.templatable(conf[CONF_TIMES], args, cg.uint32)
cg.add(var.set_send_times(template_))
template_ = await cg.templatable(conf[CONF_WAIT_TIME], args, cg.uint32)
cg.add(var.set_send_wait(template_))
await coroutine(func)(var, config, args)
return var
return registerer(new_func)
return decorator
def register_protocol_action(
name: str,
protocol: str,
schema: cv.Schema,
*fields: tuple[str, str, SafeExpType],
validate: Callable[[ConfigType], ConfigType] | None = None,
) -> None:
"""Register ``transmit_<name>`` with no C++ action class.
One generated function fills ``<protocol>Data`` from ``(conf_key, member, type_)`` fields
and sends it; the action is a core ``ApplyAction``.
"""
action, schema, validator = _transmit_action(name, schema, validate)
builder = automation.apply_action_builder(
action,
schema,
*(
automation.ApplyField(key, f"data.{member} = {{}}", type_)
for key, member, type_ in fields
),
automation.ApplyField((CONF_REPEAT, CONF_TIMES), "set_send_times", cg.uint32),
automation.ApplyField(
(CONF_REPEAT, CONF_WAIT_TIME), "set_send_wait", cg.uint32
),
call=f"protocol_call<{ns.class_(f'{protocol}Protocol')}>",
id_key=CONF_TRANSMITTER_ID,
)
async def protocol_builder(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
_request_protocol_if_in_tree(name)
return await builder(config, action_id, template_arg, args)
automation.register_action(
action, automation.ApplyAction, validator, synchronous=True
)(protocol_builder)
def declare_protocol(name):
data = ns.struct(f"{name}Data")
binary_sensor_ = ns.class_(f"{name}BinarySensor", RemoteReceiverBinarySensorBase)
trigger = ns.class_(f"{name}Trigger", RemoteReceiverTrigger)
action = ns.class_(f"{name}Action", RemoteTransmitterActionBase)
dumper = ns.class_(f"{name}Dumper", RemoteReceiverDumperBase)
return data, binary_sensor_, trigger, action, dumper
BINARY_SENSOR_REGISTRY = Registry(
binary_sensor.binary_sensor_schema().extend(
{
cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase),
}
)
)
validate_binary_sensor = cv.validate_registry_entry(
"remote receiver", BINARY_SENSOR_REGISTRY
)
TRIGGER_REGISTRY = SimpleRegistry()
DUMPER_REGISTRY = Registry()
def validate_dumpers(value):
if isinstance(value, str) and value.lower() == "all":
return validate_dumpers(list(DUMPER_REGISTRY.keys()))
entries = cv.validate_registry("dumper", DUMPER_REGISTRY)(value)
# a dumper listed twice would register twice; the receiver holds one secondary dumper
return list(
{
next(k for k in entry if k in DUMPER_REGISTRY): entry for entry in entries
}.values()
)
def validate_triggers(base_schema):
assert isinstance(base_schema, cv.Schema)
@schema_extractor("triggers")
def validator(config):
added_keys = {}
for key, (_, valid) in TRIGGER_REGISTRY.items():
added_keys[cv.Optional(key)] = valid
new_schema = base_schema.extend(added_keys)
if config == SCHEMA_EXTRACT:
return new_schema
return new_schema(config)
return validator
async def build_binary_sensor(full_config):
registry_entry, config = cg.extract_registry_entry_config(
BINARY_SENSOR_REGISTRY, full_config
)
type_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
var = cg.new_Pvariable(type_id)
await cg.register_component(var, full_config)
await register_listener(var, full_config)
await builder(var, config)
return var
async def build_triggers(full_config):
triggers = []
for key in TRIGGER_REGISTRY:
for config in full_config.get(key, []):
func = TRIGGER_REGISTRY[key][0]
triggers.append(await func(config))
return triggers
async def build_dumpers(config):
dumpers = []
for conf in config:
dumper = await cg.build_registry_entry(DUMPER_REGISTRY, conf)
dumpers.append(dumper)
return dumpers
# Beo4
Beo4Data, Beo4BinarySensor, Beo4Trigger, _, Beo4Dumper = declare_protocol("Beo4")
BEO4_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE): cv.hex_uint8_t,
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_COMMAND_REPEATS, default=1): cv.uint8_t,
}
)
@register_binary_sensor("beo4", Beo4BinarySensor, BEO4_SCHEMA)
def beo4_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
Beo4Data,
("source", config[CONF_SOURCE]),
("command", config[CONF_COMMAND]),
("repeats", config[CONF_COMMAND_REPEATS]),
)
)
)
@register_trigger("beo4", Beo4Trigger, Beo4Data)
def beo4_trigger(var, config):
pass
@register_dumper("beo4", Beo4Dumper)
def beo4_dumper(var, config):
pass
register_protocol_action(
"beo4",
"Beo4",
BEO4_SCHEMA,
(CONF_SOURCE, "source", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
(CONF_COMMAND_REPEATS, "repeats", cg.uint8),
)
# Brennenstuhl
(
BrennenstuhlData,
BrennenstuhlBinarySensor,
BrennenstuhlTrigger,
_,
BrennenstuhlDumper,
) = declare_protocol("Brennenstuhl")
BRENNENSTUHL_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.hex_uint32_t,
}
)
@register_binary_sensor("brennenstuhl", BrennenstuhlBinarySensor, BRENNENSTUHL_SCHEMA)
def brennenstuhl_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
BrennenstuhlData,
("code", config[CONF_CODE]),
)
)
)
@register_trigger("brennenstuhl", BrennenstuhlTrigger, BrennenstuhlData)
def brennenstuhl_trigger(var, config):
pass
@register_dumper("brennenstuhl", BrennenstuhlDumper)
def brennenstuhl_dumper(var, config):
pass
register_protocol_action(
"brennenstuhl",
"Brennenstuhl",
BRENNENSTUHL_SCHEMA,
(CONF_CODE, "code", cg.uint32),
)
# ByronSX
(
ByronSXData,
ByronSXBinarySensor,
ByronSXTrigger,
_,
ByronSXDumper,
) = declare_protocol("ByronSX")
BYRONSX_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFF)),
cv.Optional(CONF_COMMAND, default=0x10): cv.All(
cv.hex_int, cv.one_of(1, 2, 3, 5, 6, 9, 0xD, 0xE, 0x10, int=True)
),
}
)
@register_binary_sensor("byronsx", ByronSXBinarySensor, BYRONSX_SCHEMA)
def byronsx_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
ByronSXData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("byronsx", ByronSXTrigger, ByronSXData)
def byronsx_trigger(var, config):
pass
@register_dumper("byronsx", ByronSXDumper)
def byronsx_dumper(var, config):
pass
register_protocol_action(
"byronsx",
"ByronSX",
BYRONSX_SCHEMA,
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
# CanalSat
(
CanalSatData,
CanalSatBinarySensor,
CanalSatTrigger,
_,
CanalSatDumper,
) = declare_protocol("CanalSat")
CANALSAT_SCHEMA = cv.Schema(
{
cv.Required(CONF_DEVICE): cv.hex_uint8_t,
cv.Optional(CONF_ADDRESS, default=0): cv.hex_uint8_t,
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
}
)
@register_binary_sensor("canalsat", CanalSatBinarySensor, CANALSAT_SCHEMA)
def canalsat_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
CanalSatData,
("device", config[CONF_DEVICE]),
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("canalsat", CanalSatTrigger, CanalSatData)
def canalsat_trigger(var, config):
pass
@register_dumper("canalsat", CanalSatDumper)
def canalsat_dumper(var, config):
pass
register_protocol_action(
"canalsat",
"CanalSat",
CANALSAT_SCHEMA,
(CONF_DEVICE, "device", cg.uint8),
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
(
CanalSatLDData,
CanalSatLDBinarySensor,
CanalSatLDTrigger,
_,
CanalSatLDDumper,
) = declare_protocol("CanalSatLD")
CANALSATLD_SCHEMA = cv.Schema(
{
cv.Required(CONF_DEVICE): cv.hex_uint8_t,
cv.Optional(CONF_ADDRESS, default=0): cv.hex_uint8_t,
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
}
)
@register_binary_sensor("canalsatld", CanalSatLDBinarySensor, CANALSATLD_SCHEMA)
def canalsatld_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
CanalSatLDData,
("device", config[CONF_DEVICE]),
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("canalsatld", CanalSatLDTrigger, CanalSatLDData)
def canalsatld_trigger(var, config):
pass
@register_dumper("canalsatld", CanalSatLDDumper)
def canalsatld_dumper(var, config):
pass
register_protocol_action(
"canalsatld",
"CanalSatLD",
CANALSATLD_SCHEMA,
(CONF_DEVICE, "device", cg.uint8),
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
# Coolix
(
CoolixData,
CoolixBinarySensor,
CoolixTrigger,
_,
CoolixDumper,
) = declare_protocol("Coolix")
COOLIX_BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215),
cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215),
}
)
COOLIX_SENSOR_SCHEMA = cv.Any(cv.hex_int_range(0, 16777215), COOLIX_BASE_SCHEMA)
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SENSOR_SCHEMA)
def coolix_binary_sensor(var, config):
if isinstance(config, dict):
cg.add(
var.set_data(
cg.ArrayInitializer(
config[CONF_FIRST],
config[CONF_SECOND],
)
)
)
else:
cg.add(var.set_data(cg.ArrayInitializer(0, config)))
register_protocol_action(
"coolix",
"Coolix",
COOLIX_BASE_SCHEMA,
(CONF_FIRST, "first", cg.uint32),
(CONF_SECOND, "second", cg.uint32),
)
@register_trigger("coolix", CoolixTrigger, CoolixData)
def coolix_trigger(var, config):
pass
@register_dumper("coolix", CoolixDumper)
def coolix_dumper(var, config):
pass
# Dish
DishData, DishBinarySensor, DishTrigger, _, DishDumper = declare_protocol("Dish")
DISH_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ADDRESS, default=1): cv.int_range(min=1, max=16),
cv.Required(CONF_COMMAND): cv.int_range(min=0, max=63),
}
)
@register_binary_sensor("dish", DishBinarySensor, DISH_SCHEMA)
def dish_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
DishData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("dish", DishTrigger, DishData)
def dish_trigger(var, config):
pass
@register_dumper("dish", DishDumper)
def dish_dumper(var, config):
pass
register_protocol_action(
"dish",
"Dish",
DISH_SCHEMA,
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
# Dooya
DooyaData, DooyaBinarySensor, DooyaTrigger, _, DooyaDumper = declare_protocol("Dooya")
DOOYA_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.hex_int_range(0, 16777215),
cv.Required(CONF_CHANNEL): cv.hex_int_range(0, 255),
cv.Required(CONF_BUTTON): cv.hex_int_range(0, 15),
cv.Required(CONF_CHECK): cv.hex_int_range(0, 15),
}
)
@register_binary_sensor("dooya", DooyaBinarySensor, DOOYA_SCHEMA)
def dooya_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
DooyaData,
("id", config[CONF_ID]),
("channel", config[CONF_CHANNEL]),
("button", config[CONF_BUTTON]),
("check", config[CONF_CHECK]),
)
)
)
@register_trigger("dooya", DooyaTrigger, DooyaData)
def dooya_trigger(var, config):
pass
@register_dumper("dooya", DooyaDumper)
def dooya_dumper(var, config):
pass
register_protocol_action(
"dooya",
"Dooya",
DOOYA_SCHEMA,
(CONF_ID, "id", cg.uint32),
(CONF_CHANNEL, "channel", cg.uint8),
(CONF_BUTTON, "button", cg.uint8),
(CONF_CHECK, "check", cg.uint8),
)
# Dyson
DysonData, DysonBinarySensor, DysonTrigger, _, DysonDumper = declare_protocol("Dyson")
DYSON_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.hex_uint16_t,
cv.Optional(CONF_INDEX, default=0xFF): cv.hex_uint8_t,
}
)
@register_binary_sensor("dyson", DysonBinarySensor, DYSON_SCHEMA)
def dyson_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
DysonData,
("code", config[CONF_CODE]),
("index", config[CONF_INDEX]),
)
)
)
@register_trigger("dyson", DysonTrigger, DysonData)
def dyson_trigger(var, config):
pass
@register_dumper("dyson", DysonDumper)
def dyson_dumper(var, config):
pass
register_protocol_action(
"dyson",
"Dyson",
DYSON_SCHEMA,
(CONF_CODE, "code", cg.uint16),
(CONF_INDEX, "index", cg.uint8),
)
# JVC
JVCData, JVCBinarySensor, JVCTrigger, _, JVCDumper = declare_protocol("JVC")
JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
@register_binary_sensor("jvc", JVCBinarySensor, JVC_SCHEMA)
def jvc_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
JVCData,
("data", config[CONF_DATA]),
)
)
)
@register_trigger("jvc", JVCTrigger, JVCData)
def jvc_trigger(var, config):
pass
@register_dumper("jvc", JVCDumper)
def jvc_dumper(var, config):
pass
register_protocol_action(
"jvc",
"JVC",
JVC_SCHEMA,
(CONF_DATA, "data", cg.uint32),
)
# LG
LGData, LGBinarySensor, LGTrigger, _, LGDumper = declare_protocol("LG")
LG_SCHEMA = cv.Schema(
{
cv.Required(CONF_DATA): cv.hex_uint32_t,
cv.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True),
}
)
@register_binary_sensor("lg", LGBinarySensor, LG_SCHEMA)
def lg_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
LGData,
("data", config[CONF_DATA]),
("nbits", config[CONF_NBITS]),
)
)
)
@register_trigger("lg", LGTrigger, LGData)
def lg_trigger(var, config):
pass
@register_dumper("lg", LGDumper)
def lg_dumper(var, config):
pass
register_protocol_action(
"lg",
"LG",
LG_SCHEMA,
(CONF_DATA, "data", cg.uint32),
(CONF_NBITS, "nbits", cg.uint8),
)
# MagiQuest
(
MagiQuestData,
MagiQuestBinarySensor,
MagiQuestTrigger,
_,
MagiQuestDumper,
) = declare_protocol("MagiQuest")
MAGIQUEST_SCHEMA = cv.Schema(
{
cv.Required(CONF_WAND_ID): cv.hex_uint32_t,
cv.Optional(CONF_MAGNITUDE, default=0xFFFF): cv.hex_uint16_t,
}
)
@register_binary_sensor("magiquest", MagiQuestBinarySensor, MAGIQUEST_SCHEMA)
def magiquest_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
MagiQuestData,
("magnitude", config[CONF_MAGNITUDE]),
("wand_id", config[CONF_WAND_ID]),
)
)
)
@register_trigger("magiquest", MagiQuestTrigger, MagiQuestData)
def magiquest_trigger(var, config):
pass
@register_dumper("magiquest", MagiQuestDumper)
def magiquest_dumper(var, config):
pass
register_protocol_action(
"magiquest",
"MagiQuest",
MAGIQUEST_SCHEMA,
(CONF_WAND_ID, "wand_id", cg.uint32),
(CONF_MAGNITUDE, "magnitude", cg.uint16),
)
# Microchip HCS301 KeeLoq OOK
(
KeeloqData,
KeeloqBinarySensor,
KeeloqTrigger,
_,
KeeloqDumper,
) = declare_protocol("Keeloq")
KEELOQ_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFF)),
cv.Required(CONF_CODE): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFFF)),
cv.Optional(CONF_COMMAND, default=0x10): cv.All(
cv.hex_int,
cv.Range(min=0, max=0x10),
),
cv.Optional(CONF_LEVEL, default=False): cv.boolean,
}
)
def _keeloq_suffix_fits(config: ConfigType) -> ConfigType:
# A lambda is only known on the device. A plain value has to fit the width.
suffix = config.get(CONF_SUFFIX, 0)
bits = config.get(CONF_SUFFIX_BITS, 0)
if isinstance(suffix, int) and isinstance(bits, int) and suffix >> bits:
raise cv.Invalid(
f"suffix 0x{suffix:X} does not fit in {bits} suffix_bits", [CONF_SUFFIX]
)
return config
KEELOQ_ACTION_SCHEMA = KEELOQ_SCHEMA.extend(
{
# No defaults: an absent key leaves the data at 0 and emits nothing.
cv.Optional(CONF_SUFFIX): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
cv.Optional(CONF_SUFFIX_BITS): cv.int_range(min=0, max=16),
}
)
@register_binary_sensor("keeloq", KeeloqBinarySensor, KEELOQ_SCHEMA)
def Keeloq_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
KeeloqData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("keeloq", KeeloqTrigger, KeeloqData)
def keeloq_trigger(var, config):
pass
@register_dumper("keeloq", KeeloqDumper)
def keeloq_dumper(var, config):
pass
register_protocol_action(
"keeloq",
"Keeloq",
KEELOQ_ACTION_SCHEMA,
(CONF_ADDRESS, "address", cg.uint32),
(CONF_CODE, "encrypted", cg.uint32),
(CONF_COMMAND, "command", cg.uint8),
(CONF_LEVEL, "vlow", cg.bool_),
(CONF_SUFFIX, "suffix", cg.uint16),
(CONF_SUFFIX_BITS, "suffix_bits", cg.uint8),
validate=_keeloq_suffix_fits,
)
# NEC
NECData, NECBinarySensor, NECTrigger, _, NECDumper = declare_protocol("NEC")
NEC_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Required(CONF_COMMAND): cv.hex_uint16_t,
cv.Optional(CONF_COMMAND_REPEATS, default=1): cv.uint16_t,
}
)
@register_binary_sensor("nec", NECBinarySensor, NEC_SCHEMA)
def nec_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
NECData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
("command_repeats", config[CONF_COMMAND_REPEATS]),
)
)
)
@register_trigger("nec", NECTrigger, NECData)
def nec_trigger(var, config):
pass
@register_dumper("nec", NECDumper)
def nec_dumper(var, config):
pass
register_protocol_action(
"nec",
"NEC",
NEC_SCHEMA,
(CONF_ADDRESS, "address", cg.uint16),
(CONF_COMMAND, "command", cg.uint16),
(CONF_COMMAND_REPEATS, "command_repeats", cg.uint16),
)
# Onkyo RI
OnkyoRIData, OnkyoRIBinarySensor, OnkyoRITrigger, _, OnkyoRIDumper = declare_protocol(
"OnkyoRI"
)
ONKYORI_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_int_range(0, 0xFFF)})
@register_binary_sensor("onkyori", OnkyoRIBinarySensor, ONKYORI_SCHEMA)
def onkyori_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
OnkyoRIData,
("data", config[CONF_DATA]),
)
)
)
@register_trigger("onkyori", OnkyoRITrigger, OnkyoRIData)
def onkyori_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("onkyori", OnkyoRIDumper)
def onkyori_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
register_protocol_action(
"onkyori",
"OnkyoRI",
ONKYORI_SCHEMA,
(CONF_DATA, "data", cg.uint16),
)
# Pioneer
(
PioneerData,
PioneerBinarySensor,
PioneerTrigger,
_,
PioneerDumper,
) = declare_protocol("Pioneer")
PIONEER_SCHEMA = cv.Schema(
{
cv.Required(CONF_RC_CODE_1): cv.hex_uint16_t,
cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_uint16_t,
}
)
@register_binary_sensor("pioneer", PioneerBinarySensor, PIONEER_SCHEMA)
def pioneer_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
PioneerData,
("rc_code_1", config[CONF_RC_CODE_1]),
("rc_code_2", config[CONF_RC_CODE_2]),
)
)
)
@register_trigger("pioneer", PioneerTrigger, PioneerData)
def pioneer_trigger(var, config):
pass
@register_dumper("pioneer", PioneerDumper)
def pioneer_dumper(var, config):
pass
register_protocol_action(
"pioneer",
"Pioneer",
PIONEER_SCHEMA,
(CONF_RC_CODE_1, "rc_code_1", cg.uint16),
(CONF_RC_CODE_2, "rc_code_2", cg.uint16),
)
# Pronto
(
ProntoData,
ProntoBinarySensor,
ProntoTrigger,
ProntoAction,
ProntoDumper,
) = declare_protocol("Pronto")
PRONTO_SCHEMA = cv.Schema(
{
cv.Required(CONF_DATA): cv.string,
cv.Optional(CONF_DELTA, default=-1): cv.int_,
}
)
@register_binary_sensor("pronto", ProntoBinarySensor, PRONTO_SCHEMA)
def pronto_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
ProntoData,
("data", config[CONF_DATA]),
("delta", config[CONF_DELTA]),
)
)
)
@register_trigger("pronto", ProntoTrigger, ProntoData)
def pronto_trigger(var, config):
pass
@register_dumper("pronto", ProntoDumper)
def pronto_dumper(var, config):
pass
@register_action("pronto", ProntoAction, PRONTO_SCHEMA)
async def pronto_action(var, config, args):
template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string)
cg.add(var.set_data(template_))
# Gobox
(
GoboxData,
GoboxBinarySensor,
GoboxTrigger,
_,
GoboxDumper,
) = declare_protocol("Gobox")
GOBOX_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.int_,
}
)
@register_binary_sensor("gobox", GoboxBinarySensor, GOBOX_SCHEMA)
def gobox_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
GoboxData,
("code", config[CONF_CODE]),
)
)
)
@register_trigger("gobox", GoboxTrigger, GoboxData)
def gobox_trigger(var, config):
pass
@register_dumper("gobox", GoboxDumper)
def gobox_dumper(var, config):
pass
register_protocol_action(
"gobox",
"Gobox",
GOBOX_SCHEMA,
(CONF_CODE, "code", cg.uint64),
)
# Roomba
(
RoombaData,
RoombaBinarySensor,
RoombaTrigger,
_,
RoombaDumper,
) = declare_protocol("Roomba")
ROOMBA_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint8_t})
@register_binary_sensor("roomba", RoombaBinarySensor, ROOMBA_SCHEMA)
def roomba_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
RoombaData,
("data", config[CONF_DATA]),
)
)
)
@register_trigger("roomba", RoombaTrigger, RoombaData)
def roomba_trigger(var, config):
pass
@register_dumper("roomba", RoombaDumper)
def roomba_dumper(var, config):
pass
register_protocol_action(
"roomba",
"Roomba",
ROOMBA_SCHEMA,
(CONF_DATA, "data", cg.uint8),
)
# Sony
SonyData, SonyBinarySensor, SonyTrigger, _, SonyDumper = declare_protocol("Sony")
SONY_SCHEMA = cv.Schema(
{
cv.Required(CONF_DATA): cv.hex_uint32_t,
cv.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True),
}
)
@register_binary_sensor("sony", SonyBinarySensor, SONY_SCHEMA)
def sony_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
SonyData,
("data", config[CONF_DATA]),
("nbits", config[CONF_NBITS]),
)
)
)
@register_trigger("sony", SonyTrigger, SonyData)
def sony_trigger(var, config):
pass
@register_dumper("sony", SonyDumper)
def sony_dumper(var, config):
pass
register_protocol_action(
"sony",
"Sony",
SONY_SCHEMA,
(CONF_DATA, "data", cg.uint32),
(CONF_NBITS, "nbits", cg.uint8),
)
# Symphony
SymphonyData, SymphonyBinarySensor, SymphonyTrigger, _, SymphonyDumper = (
declare_protocol("Symphony")
)
SYMPHONY_SCHEMA = cv.Schema(
{
cv.Required(CONF_DATA): cv.hex_uint32_t,
cv.Required(CONF_NBITS): cv.int_range(min=1, max=32),
cv.Optional(CONF_COMMAND_REPEATS, default=2): cv.uint8_t,
}
)
@register_binary_sensor("symphony", SymphonyBinarySensor, SYMPHONY_SCHEMA)
def symphony_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
SymphonyData,
("data", config[CONF_DATA]),
("nbits", config[CONF_NBITS]),
)
)
)
@register_trigger("symphony", SymphonyTrigger, SymphonyData)
def symphony_trigger(var, config):
pass
@register_dumper("symphony", SymphonyDumper)
def symphony_dumper(var, config):
pass
register_protocol_action(
"symphony",
"Symphony",
SYMPHONY_SCHEMA,
(CONF_DATA, "data", cg.uint32),
(CONF_NBITS, "nbits", cg.uint8),
(CONF_COMMAND_REPEATS, "repeats", cg.uint8),
)
# Raw
def validate_raw_alternating(value):
assert isinstance(value, list)
last_negative = None
for i, val in enumerate(value):
this_negative = val < 0
if i != 0 and this_negative == last_negative:
raise cv.Invalid(
f"Values must alternate between being positive and negative, please see index {i - 1} and {i}",
[i],
)
last_negative = this_negative
return value
RawData, RawBinarySensor, RawTrigger, RawAction, RawDumper = declare_protocol("Raw")
CONF_CODE_STORAGE_ID = "code_storage_id"
RAW_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.All(
[cv.Any(cv.int_, cv.time_period_microseconds)],
cv.Length(min=1),
validate_raw_alternating,
),
cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.int32),
}
)
@register_binary_sensor("raw", RawBinarySensor, RAW_SCHEMA)
def raw_binary_sensor(var, config):
code_ = config[CONF_CODE]
arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_)
cg.add(var.set_data(arr))
cg.add(var.set_len(len(code_)))
@register_trigger("raw", RawTrigger, cg.std_vector.template(cg.int32))
def raw_trigger(var, config):
pass
@register_dumper("raw", RawDumper)
def raw_dumper(var, config):
pass
@register_action(
"raw",
RawAction,
RAW_SCHEMA.extend(
{
cv.Optional(CONF_CARRIER_FREQUENCY, default="0Hz"): cv.All(
cv.frequency, cv.int_
),
}
),
)
async def raw_action(var, config, args):
code_ = config[CONF_CODE]
if cg.is_template(code_):
template_ = await cg.templatable(code_, args, cg.std_vector.template(cg.int32))
cg.add(var.set_code_template(template_))
else:
code_ = config[CONF_CODE]
arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_)
cg.add(var.set_code_static(arr, len(code_)))
templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32)
cg.add(var.set_carrier_frequency(templ))
# Drayton
(
DraytonData,
DraytonBinarySensor,
DraytonTrigger,
_,
DraytonDumper,
) = declare_protocol("Drayton")
DRAYTON_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
}
)
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
def drayton_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
DraytonData,
("address", config[CONF_ADDRESS]),
("channel", config[CONF_CHANNEL]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("drayton", DraytonTrigger, DraytonData)
def drayton_trigger(var, config):
pass
@register_dumper("drayton", DraytonDumper)
def drayton_dumper(var, config):
pass
register_protocol_action(
"drayton",
"Drayton",
DRAYTON_SCHEMA,
(CONF_ADDRESS, "address", cg.uint16),
(CONF_CHANNEL, "channel", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
# RC5
RC5Data, RC5BinarySensor, RC5Trigger, _, RC5Dumper = declare_protocol("RC5")
RC5_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
}
)
@register_binary_sensor("rc5", RC5BinarySensor, RC5_SCHEMA)
def rc5_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
RC5Data,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("rc5", RC5Trigger, RC5Data)
def rc5_trigger(var, config):
pass
@register_dumper("rc5", RC5Dumper)
def rc5_dumper(var, config):
pass
register_protocol_action(
"rc5",
"RC5",
RC5_SCHEMA,
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
)
# RC5X (aka RC5 Marantz)
RC5XData, RC5XBinarySensor, RC5XTrigger, _, RC5XDumper = declare_protocol("RC5X")
RC5X_SCHEMA = RC5_SCHEMA.extend(
{cv.Required(CONF_EXTENSION): cv.hex_int_range(min=0, max=0x3F)}
)
@register_binary_sensor("rc5x", RC5XBinarySensor, RC5X_SCHEMA)
def rc5x_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
RC5XData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
("extension", config[CONF_EXTENSION]),
)
)
)
@register_trigger("rc5x", RC5XTrigger, RC5XData)
def rc5x_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("rc5x", RC5XDumper)
def rc5x_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
register_protocol_action(
"rc5x",
"RC5X",
RC5X_SCHEMA,
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
(CONF_EXTENSION, "extension", cg.uint8),
)
# RC6
RC6Data, RC6BinarySensor, RC6Trigger, RC6Action, RC6Dumper = declare_protocol("RC6")
RC6_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_uint8_t,
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
}
)
@register_binary_sensor("rc6", RC6BinarySensor, RC6_SCHEMA)
def rc6_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
RC6Data,
("mode", 0),
("toggle", 0),
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("rc6", RC6Trigger, RC6Data)
def rc6_trigger(var, config):
pass
@register_dumper("rc6", RC6Dumper)
def rc6_dumper(var, config):
pass
@register_action("rc6", RC6Action, RC6_SCHEMA)
async def rc6_action(var, config, args):
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
cg.add(var.set_address(template_))
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
cg.add(var.set_command(template_))
# RC Switch Raw
RC_SWITCH_TIMING_SCHEMA = cv.All([cv.uint8_t], cv.Length(min=2, max=2))
RC_SWITCH_PROTOCOL_SCHEMA = cv.Any(
cv.int_range(min=1, max=8),
cv.Schema(
{
cv.Required(CONF_PULSE_LENGTH): cv.uint32_t,
cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA,
cv.Optional(CONF_ZERO, default=[1, 3]): RC_SWITCH_TIMING_SCHEMA,
cv.Optional(CONF_ONE, default=[3, 1]): RC_SWITCH_TIMING_SCHEMA,
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
),
)
def validate_rc_switch_code(value):
if not isinstance(value, str):
raise cv.Invalid("All RCSwitch codes must be in quotes ('')")
for c in value:
if c not in ("0", "1"):
raise cv.Invalid(
f"Invalid RCSwitch code character '{c}'. Only '0' and '1' are allowed"
)
if len(value) > 64:
raise cv.Invalid(
f"Maximum length for RCSwitch codes is 64, code '{value}' has length {len(value)}"
)
if not value:
raise cv.Invalid("RCSwitch code must not be empty")
return value
def validate_rc_switch_raw_code(value):
if not isinstance(value, str):
raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')")
for c in value:
if c not in ("0", "1", "x"):
raise cv.Invalid(
f"Invalid RCSwitch raw code character '{c}'.Only '0', '1' and 'x' are allowed"
)
if len(value) > 64:
raise cv.Invalid(
f"Maximum length for RCSwitch raw codes is 64, code '{value}' has length {len(value)}"
)
if not value:
raise cv.Invalid("RCSwitch raw code must not be empty")
return value
def build_custom_rc_switch_protocol(config: ConfigType) -> MockObj:
pl = config[CONF_PULSE_LENGTH]
return RCSwitchBase(
config[CONF_SYNC][0] * pl,
config[CONF_SYNC][1] * pl,
config[CONF_ZERO][0] * pl,
config[CONF_ZERO][1] * pl,
config[CONF_ONE][0] * pl,
config[CONF_ONE][1] * pl,
config[CONF_INVERTED],
)
def rc_switch_protocol_in_flash(config: int | ConfigType) -> MockObj:
"""Pointer to the protocol in flash: a built-in table entry or a shared custom table."""
if isinstance(config, int):
return cg.RawExpression(f"&{RC_SWITCH_PROTOCOLS}[{config}]")
return cg.shared_progmem_array(
"rc_switch_custom_protocol",
RCSwitchBase,
[build_custom_rc_switch_protocol(config)],
)
def rc_switch_protocol_value(config: int | ConfigType) -> MockObj:
"""RAM copy of a constant protocol for the transmit actions, read from its flash table."""
if isinstance(config, int):
return rc_switch_protocol(config)
return rc_switch_protocol_copy(rc_switch_protocol_in_flash(config))
RC_SWITCH_RAW_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): validate_rc_switch_raw_code,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}
)
RC_SWITCH_TYPE_A_SCHEMA = cv.Schema(
{
cv.Required(CONF_GROUP): cv.All(
validate_rc_switch_code, cv.Length(min=5, max=5)
),
cv.Required(CONF_DEVICE): cv.All(
validate_rc_switch_code, cv.Length(min=5, max=5)
),
cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}
)
RC_SWITCH_TYPE_B_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.int_range(min=1, max=4),
cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=4),
cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}
)
RC_SWITCH_TYPE_C_SCHEMA = cv.Schema(
{
cv.Required(CONF_FAMILY): cv.one_of(
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
lower=True,
),
cv.Required(CONF_GROUP): cv.int_range(min=1, max=4),
cv.Required(CONF_DEVICE): cv.int_range(min=1, max=4),
cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}
)
RC_SWITCH_TYPE_D_SCHEMA = cv.Schema(
{
cv.Required(CONF_GROUP): cv.one_of("a", "b", "c", "d", lower=True),
cv.Required(CONF_DEVICE): cv.int_range(min=1, max=3),
cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}
)
RC_SWITCH_TRANSMITTER = cv.Schema(
{
cv.Optional(CONF_REPEAT, default={CONF_TIMES: 5}): cv.Schema(
{
cv.Required(CONF_TIMES): cv.templatable(cv.positive_int),
cv.Optional(CONF_WAIT_TIME, default="0us"): cv.templatable(
cv.positive_time_period_microseconds
),
}
),
}
)
rc_switch_protocol = ns.rc_switch_protocol
rc_switch_protocol_copy = ns.rc_switch_protocol_copy
RC_SWITCH_PROTOCOLS = ns.RC_SWITCH_PROTOCOLS
RCSwitchData = ns.struct("RCSwitchData")
RCSwitchBase = ns.class_("RCSwitchBase")
RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger)
RCSwitchDumper = ns.class_("RCSwitchDumper", RemoteReceiverDumperBase)
RCSwitchRawAction = ns.class_("RCSwitchRawAction", RemoteTransmitterActionBase)
RCSwitchTypeAAction = ns.class_("RCSwitchTypeAAction", RemoteTransmitterActionBase)
RCSwitchTypeBAction = ns.class_("RCSwitchTypeBAction", RemoteTransmitterActionBase)
RCSwitchTypeCAction = ns.class_("RCSwitchTypeCAction", RemoteTransmitterActionBase)
RCSwitchTypeDAction = ns.class_("RCSwitchTypeDAction", RemoteTransmitterActionBase)
RCSwitchRawReceiver = ns.class_("RCSwitchRawReceiver", RemoteReceiverBinarySensorBase)
@register_binary_sensor("rc_switch_raw", RCSwitchRawReceiver, RC_SWITCH_RAW_SCHEMA)
def rc_switch_raw_binary_sensor(var, config):
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
cg.add(var.set_code(config[CONF_CODE]))
@register_action(
"rc_switch_raw",
RCSwitchRawAction,
RC_SWITCH_RAW_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
async def rc_switch_raw_action(var, config, args):
proto = await cg.templatable(
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
)
cg.add(var.set_protocol(proto))
cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string)))
@register_binary_sensor(
"rc_switch_type_a", RCSwitchRawReceiver, RC_SWITCH_TYPE_A_SCHEMA
)
def rc_switch_type_a_binary_sensor(var, config):
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
@register_action(
"rc_switch_type_a",
RCSwitchTypeAAction,
RC_SWITCH_TYPE_A_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
async def rc_switch_type_a_action(var, config, args):
proto = await cg.templatable(
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
)
cg.add(var.set_protocol(proto))
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string)))
cg.add(
var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.std_string))
)
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.bool_)))
@register_binary_sensor(
"rc_switch_type_b", RCSwitchRawReceiver, RC_SWITCH_TYPE_B_SCHEMA
)
def rc_switch_type_b_binary_sensor(var, config):
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
cg.add(
var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE])
)
@register_action(
"rc_switch_type_b",
RCSwitchTypeBAction,
RC_SWITCH_TYPE_B_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
async def rc_switch_type_b_action(var, config, args):
proto = await cg.templatable(
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
)
cg.add(var.set_protocol(proto))
cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)))
cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)))
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.bool_)))
@register_binary_sensor(
"rc_switch_type_c", RCSwitchRawReceiver, RC_SWITCH_TYPE_C_SCHEMA
)
def rc_switch_type_c_binary_sensor(var, config):
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
cg.add(
var.set_type_c(
config[CONF_FAMILY],
config[CONF_GROUP],
config[CONF_DEVICE],
config[CONF_STATE],
)
)
@register_action(
"rc_switch_type_c",
RCSwitchTypeCAction,
RC_SWITCH_TYPE_C_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
async def rc_switch_type_c_action(var, config, args):
proto = await cg.templatable(
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
)
cg.add(var.set_protocol(proto))
cg.add(
var.set_family(await cg.templatable(config[CONF_FAMILY], args, cg.std_string))
)
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8)))
cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.bool_)))
@register_binary_sensor(
"rc_switch_type_d",
RCSwitchRawReceiver,
RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
def rc_switch_type_d_binary_sensor(var, config):
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
@register_action(
"rc_switch_type_d",
RCSwitchTypeDAction,
RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
)
async def rc_switch_type_d_action(var, config, args):
proto = await cg.templatable(
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
)
cg.add(var.set_protocol(proto))
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string)))
cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.bool_)))
@register_trigger("rc_switch", RCSwitchTrigger, RCSwitchData)
def rc_switch_trigger(var, config):
pass
@register_dumper("rc_switch", RCSwitchDumper)
def rc_switch_dumper(var, config):
pass
# Samsung
(
SamsungData,
SamsungBinarySensor,
SamsungTrigger,
_,
SamsungDumper,
) = declare_protocol("Samsung")
SAMSUNG_SCHEMA = cv.Schema(
{
cv.Required(CONF_DATA): cv.hex_uint64_t,
cv.Optional(CONF_NBITS, default=32): cv.int_range(32, 64),
}
)
@register_binary_sensor("samsung", SamsungBinarySensor, SAMSUNG_SCHEMA)
def samsung_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
SamsungData,
("data", config[CONF_DATA]),
("nbits", config[CONF_NBITS]),
)
)
)
@register_trigger("samsung", SamsungTrigger, SamsungData)
def samsung_trigger(var, config):
pass
@register_dumper("samsung", SamsungDumper)
def samsung_dumper(var, config):
pass
register_protocol_action(
"samsung",
"Samsung",
SAMSUNG_SCHEMA,
(CONF_DATA, "data", cg.uint64),
(CONF_NBITS, "nbits", cg.uint8),
)
# Samsung36
(
Samsung36Data,
Samsung36BinarySensor,
Samsung36Trigger,
_,
Samsung36Dumper,
) = declare_protocol("Samsung36")
SAMSUNG36_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Required(CONF_COMMAND): cv.hex_uint32_t,
}
)
@register_binary_sensor("samsung36", Samsung36BinarySensor, SAMSUNG36_SCHEMA)
def samsung36_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
Samsung36Data,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("samsung36", Samsung36Trigger, Samsung36Data)
def samsung36_trigger(var, config):
pass
@register_dumper("samsung36", Samsung36Dumper)
def samsung36_dumper(var, config):
pass
register_protocol_action(
"samsung36",
"Samsung36",
SAMSUNG36_SCHEMA,
(CONF_ADDRESS, "address", cg.uint16),
(CONF_COMMAND, "command", cg.uint32),
)
# Toshiba AC
(
ToshibaAcData,
ToshibaAcBinarySensor,
ToshibaAcTrigger,
_,
ToshibaAcDumper,
) = declare_protocol("ToshibaAc")
TOSHIBAAC_SCHEMA = cv.Schema(
{
cv.Required(CONF_RC_CODE_1): cv.hex_uint64_t,
cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_uint64_t,
}
)
@register_binary_sensor("toshiba_ac", ToshibaAcBinarySensor, TOSHIBAAC_SCHEMA)
def toshibaac_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
ToshibaAcData,
("rc_code_1", config[CONF_RC_CODE_1]),
("rc_code_2", config[CONF_RC_CODE_2]),
)
)
)
@register_trigger("toshiba_ac", ToshibaAcTrigger, ToshibaAcData)
def toshibaac_trigger(var, config):
pass
@register_dumper("toshiba_ac", ToshibaAcDumper)
def toshibaac_dumper(var, config):
pass
register_protocol_action(
"toshiba_ac",
"ToshibaAc",
TOSHIBAAC_SCHEMA,
(CONF_RC_CODE_1, "rc_code_1", cg.uint64),
(CONF_RC_CODE_2, "rc_code_2", cg.uint64),
)
# Panasonic
(
PanasonicData,
PanasonicBinarySensor,
PanasonicTrigger,
_,
PanasonicDumper,
) = declare_protocol("Panasonic")
PANASONIC_SCHEMA = cv.Schema(
{
cv.Optional(CONF_NBITS, default=48): cv.one_of(48, 56, int=True),
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Optional(CONF_ADDRESS_2, default=0x00): cv.hex_uint8_t,
cv.Required(CONF_COMMAND): cv.hex_uint32_t,
}
)
def _panasonic_address_2_needs_56_bits(config: ConfigType) -> ConfigType:
# A 48-bit code has no second address byte; a lambda is only known on the device
address_2 = config[CONF_ADDRESS_2]
if config[CONF_NBITS] == 48 and isinstance(address_2, int) and address_2 != 0:
raise cv.Invalid(
f"{CONF_ADDRESS_2} is only sent with {CONF_NBITS}: 56", [CONF_ADDRESS_2]
)
return config
# Transmit only
PANASONIC_ACTION_SCHEMA = PANASONIC_SCHEMA.extend(
{
cv.Optional(CONF_CARRIER_FREQUENCY, default="35000Hz"): cv.All(
cv.frequency, cv.uint32_t
),
}
)
@register_binary_sensor(
"panasonic",
PanasonicBinarySensor,
cv.All(PANASONIC_SCHEMA, _panasonic_address_2_needs_56_bits),
)
def panasonic_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
PanasonicData,
("address", config[CONF_ADDRESS]),
("command", config[CONF_COMMAND]),
("nbits", config[CONF_NBITS]),
("address_2", config[CONF_ADDRESS_2]),
)
)
)
@register_trigger("panasonic", PanasonicTrigger, PanasonicData)
def panasonic_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("panasonic", PanasonicDumper)
def panasonic_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
register_protocol_action(
"panasonic",
"Panasonic",
PANASONIC_ACTION_SCHEMA,
(CONF_NBITS, "nbits", cg.uint8),
(CONF_ADDRESS, "address", cg.uint16),
(CONF_ADDRESS_2, "address_2", cg.uint8),
(CONF_COMMAND, "command", cg.uint32),
(CONF_CARRIER_FREQUENCY, "carrier_frequency", cg.uint32),
validate=_panasonic_address_2_needs_56_bits,
)
# SomfyRts
SOMFY_RTS_SCHEMA = cv.Schema(
{
cv.Required(CONF_KEY): cv.hex_uint8_t,
cv.Required(CONF_CONTROL): cv.All(cv.hex_uint8_t, cv.Range(max=0x0F)),
cv.Required(CONF_ROLLING_CODE): cv.hex_uint16_t,
cv.Required(CONF_ADDRESS): cv.All(cv.hex_uint32_t, cv.Range(max=0xFFFFFF)),
cv.Optional(CONF_CONTROL_IN_KEY, default=False): cv.boolean,
}
)
def _validate_somfy_rts(config: ConfigType) -> ConfigType:
key = config[CONF_KEY]
if config[CONF_CONTROL_IN_KEY] is True and isinstance(key, int) and key & 0x0F:
raise cv.Invalid(
f"With {CONF_CONTROL_IN_KEY} the low nibble of {CONF_KEY} carries {CONF_CONTROL} "
"and must be 0",
path=[CONF_KEY],
)
return config
# Transmit only: the protocol has no decoder yet, so no binary sensor, trigger or dumper.
register_protocol_action(
"somfy_rts",
"SomfyRts",
SOMFY_RTS_SCHEMA,
(CONF_KEY, "key", cg.uint8),
(CONF_CONTROL, "control", cg.uint8),
(CONF_ROLLING_CODE, "rolling_code", cg.uint16),
(CONF_ADDRESS, "address", cg.uint32),
(CONF_CONTROL_IN_KEY, "control_in_key", cg.bool_),
validate=_validate_somfy_rts,
)
# Nexa
NexaData, NexaBinarySensor, NexaTrigger, _, NexaDumper = declare_protocol("Nexa")
NEXA_SCHEMA = cv.Schema(
{
cv.Required(CONF_DEVICE): cv.hex_uint32_t,
cv.Required(CONF_GROUP): cv.hex_uint8_t,
cv.Required(CONF_STATE): cv.hex_uint8_t,
cv.Required(CONF_CHANNEL): cv.hex_uint8_t,
cv.Required(CONF_LEVEL): cv.hex_uint8_t,
}
)
@register_binary_sensor("nexa", NexaBinarySensor, NEXA_SCHEMA)
def nexa_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
NexaData,
("device", config[CONF_DEVICE]),
("group", config[CONF_GROUP]),
("state", config[CONF_STATE]),
("channel", config[CONF_CHANNEL]),
("level", config[CONF_LEVEL]),
)
)
)
@register_trigger("nexa", NexaTrigger, NexaData)
def nexa_trigger(var, config):
pass
@register_dumper("nexa", NexaDumper)
def nexa_dumper(var, config):
pass
register_protocol_action(
"nexa",
"Nexa",
NEXA_SCHEMA,
(CONF_DEVICE, "device", cg.uint32),
(CONF_GROUP, "group", cg.uint8),
(CONF_STATE, "state", cg.uint8),
(CONF_CHANNEL, "channel", cg.uint8),
(CONF_LEVEL, "level", cg.uint8),
)
# Nexus
NexusData, NexusBinarySensor, NexusTrigger, _, NexusDumper = declare_protocol("Nexus")
NEXUS_SCHEMA = cv.Schema(
{
cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=4),
cv.Required(CONF_ADDRESS): cv.hex_uint8_t,
}
)
NEXUS_ACTION_SCHEMA = NEXUS_SCHEMA.extend(
{
cv.Required(CONF_TEMPERATURE): cv.All(
cv.float_, cv.Range(min=-204.8, max=204.7)
),
cv.Required(CONF_HUMIDITY): cv.uint8_t,
cv.Optional(CONF_BATTERY_LEVEL, default=True): cv.boolean,
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
}
)
@register_binary_sensor("nexus", NexusBinarySensor, NEXUS_SCHEMA)
def nexus_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
NexusData,
("address", config[CONF_ADDRESS]),
("channel", config[CONF_CHANNEL]),
)
)
)
@register_trigger("nexus", NexusTrigger, NexusData)
def nexus_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("nexus", NexusDumper)
def nexus_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
register_protocol_action(
"nexus",
"Nexus",
NEXUS_ACTION_SCHEMA,
(CONF_CHANNEL, "channel", cg.uint8),
(CONF_ADDRESS, "address", cg.uint8),
(CONF_TEMPERATURE, "temperature", cg.float_),
(CONF_HUMIDITY, "humidity", cg.uint8),
(CONF_BATTERY_LEVEL, "battery_level", cg.bool_),
(CONF_FORCE_UPDATE, "force_update", cg.bool_),
)
# Midea
MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol(
"Midea"
)
MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase)
MIDEA_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=5, max=5)),
}
)
@register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA)
def midea_binary_sensor(var, config):
cg.add(var.set_data(config[CONF_CODE]))
@register_trigger("midea", MideaTrigger, MideaData)
def midea_trigger(var, config):
pass
@register_dumper("midea", MideaDumper)
def midea_dumper(var, config):
pass
async def _code_bytes(
value: Any, args: TemplateArgsType, set_template: MockObj, set_static: MockObj
) -> None:
"""Constant codes share one PROGMEM table pool across the remote protocols."""
await automation.templatable_bytes(
value, args, set_template, set_static, "remote_base_code"
)
@register_action("midea", MideaAction, MIDEA_SCHEMA)
async def midea_action(var, config, args):
await _code_bytes(
config[CONF_CODE], args, var.set_code_template, var.set_code_static
)
# AEHA
AEHAData, AEHABinarySensor, AEHATrigger, AEHAAction, AEHADumper = declare_protocol(
"AEHA"
)
AEHA_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Required(CONF_DATA): cv.All([cv.hex_uint8_t], cv.Length(min=2, max=35)),
}
)
@register_binary_sensor("aeha", AEHABinarySensor, AEHA_SCHEMA)
def aeha_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
AEHAData,
("address", config[CONF_ADDRESS]),
("data", config[CONF_DATA]),
)
)
)
@register_trigger("aeha", AEHATrigger, AEHAData)
def aeha_trigger(var, config):
pass
@register_dumper("aeha", AEHADumper)
def aeha_dumper(var, config):
pass
@register_action(
"aeha",
AEHAAction,
AEHA_SCHEMA.extend(
{
cv.Optional(CONF_CARRIER_FREQUENCY, default="38000Hz"): cv.All(
cv.frequency, cv.int_
),
}
),
)
async def aeha_action(var, config, args):
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
cg.add(var.set_address(template_))
await _code_bytes(
config[CONF_DATA], args, var.set_data_template, var.set_data_static
)
templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32)
cg.add(var.set_carrier_frequency(templ))
# Hob2Hood
(
Hob2HoodData,
Hob2HoodBinarySensor,
Hob2HoodTrigger,
_,
Hob2HoodDumper,
) = declare_protocol("Hob2Hood")
Hob2HoodCommand = remote_base_ns.enum("Hob2HoodCommand")
HOB2HOOD_COMMAND_OPTIONS = {
"light_off": Hob2HoodCommand.HOB2HOOD_COMMAND_LIGHT_OFF,
"light_on": Hob2HoodCommand.HOB2HOOD_COMMAND_LIGHT_ON,
"fan_off": Hob2HoodCommand.HOB2HOOD_COMMAND_FAN_OFF,
"fan_low": Hob2HoodCommand.HOB2HOOD_COMMAND_FAN_LOW,
"fan_medium": Hob2HoodCommand.HOB2HOOD_COMMAND_FAN_MEDIUM,
"fan_high": Hob2HoodCommand.HOB2HOOD_COMMAND_FAN_HIGH,
"fan_max": Hob2HoodCommand.HOB2HOOD_COMMAND_FAN_MAX,
}
HOB2HOOD_SCHEMA = cv.Schema(
{cv.Required(CONF_COMMAND): cv.enum(HOB2HOOD_COMMAND_OPTIONS, lower=True)}
)
@register_binary_sensor("hob2hood", Hob2HoodBinarySensor, HOB2HOOD_SCHEMA)
def hob2hood_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
Hob2HoodData,
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("hob2hood", Hob2HoodTrigger, Hob2HoodData)
def hob2hood_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("hob2hood", Hob2HoodDumper)
def hob2hood_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
register_protocol_action(
"hob2hood",
"Hob2Hood",
HOB2HOOD_SCHEMA,
(CONF_COMMAND, "command", Hob2HoodCommand),
)
# Haier
HaierData, HaierBinarySensor, HaierTrigger, HaierAction, HaierDumper = declare_protocol(
"Haier"
)
HaierAction = ns.class_("HaierAction", RemoteTransmitterActionBase)
HAIER_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.All(
[cv.hex_uint8_t],
cv.Any(
cv.Length(min=8, max=8),
cv.Length(min=13, max=13),
msg="must be a list of length 8 or 13",
),
),
}
)
@register_binary_sensor("haier", HaierBinarySensor, HAIER_SCHEMA)
def haier_binary_sensor(var, config):
cg.add(var.set_code(config[CONF_CODE]))
@register_trigger("haier", HaierTrigger, HaierData)
def haier_trigger(var, config):
pass
@register_dumper("haier", HaierDumper)
def haier_dumper(var, config):
pass
@register_action("haier", HaierAction, HAIER_SCHEMA)
async def haier_action(var, config, args):
await _code_bytes(
config[CONF_CODE], args, var.set_code_template, var.set_code_static
)
# ABBWelcome
(
ABBWelcomeData,
ABBWelcomeBinarySensor,
ABBWelcomeTrigger,
ABBWelcomeAction,
ABBWelcomeDumper,
) = declare_protocol("ABBWelcome")
CONF_SOURCE_ADDRESS = "source_address"
CONF_DESTINATION_ADDRESS = "destination_address"
CONF_THREE_BYTE_ADDRESS = "three_byte_address"
CONF_MESSAGE_TYPE = "message_type"
CONF_MESSAGE_ID = "message_id"
CONF_RETRANSMISSION = "retransmission"
ABB_WELCOME_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE_ADDRESS): cv.hex_uint32_t,
cv.Required(CONF_DESTINATION_ADDRESS): cv.hex_uint32_t,
cv.Optional(CONF_RETRANSMISSION, default=False): cv.boolean,
cv.Optional(CONF_THREE_BYTE_ADDRESS, default=False): cv.boolean,
cv.Required(CONF_MESSAGE_TYPE): cv.Any(cv.hex_uint8_t, cv.uint8_t),
cv.Optional(CONF_MESSAGE_ID): cv.Any(cv.hex_uint8_t, cv.uint8_t),
cv.Optional(CONF_DATA): cv.All(
[cv.Any(cv.hex_uint8_t, cv.uint8_t)],
cv.Length(min=0, max=7),
),
}
)
@register_binary_sensor("abbwelcome", ABBWelcomeBinarySensor, ABB_WELCOME_SCHEMA)
def abbwelcome_binary_sensor(var, config):
cg.add(var.set_three_byte_address(config[CONF_THREE_BYTE_ADDRESS]))
cg.add(var.set_source_address(config[CONF_SOURCE_ADDRESS]))
cg.add(var.set_destination_address(config[CONF_DESTINATION_ADDRESS]))
cg.add(var.set_retransmission(config[CONF_RETRANSMISSION]))
cg.add(var.set_message_type(config[CONF_MESSAGE_TYPE]))
cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config))
if CONF_MESSAGE_ID in config:
cg.add(var.set_message_id(config[CONF_MESSAGE_ID]))
if CONF_DATA in config:
cg.add(var.set_data(config[CONF_DATA]))
cg.add(var.finalize())
@register_trigger("abbwelcome", ABBWelcomeTrigger, ABBWelcomeData)
def abbwelcome_trigger(var, config):
pass
@register_dumper("abbwelcome", ABBWelcomeDumper)
def abbwelcome_dumper(var, config):
pass
@register_action("abbwelcome", ABBWelcomeAction, ABB_WELCOME_SCHEMA)
async def abbwelcome_action(var, config, args):
cg.add(
var.set_three_byte_address(
await cg.templatable(config[CONF_THREE_BYTE_ADDRESS], args, cg.bool_)
)
)
cg.add(
var.set_source_address(
await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint32)
)
)
cg.add(
var.set_destination_address(
await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint32)
)
)
cg.add(
var.set_retransmission(
await cg.templatable(config[CONF_RETRANSMISSION], args, cg.bool_)
)
)
cg.add(
var.set_message_type(
await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8)
)
)
template_ = await cg.templatable(CONF_MESSAGE_ID not in config, args, cg.bool_)
cg.add(var.set_auto_message_id(template_))
if CONF_MESSAGE_ID in config:
cg.add(
var.set_message_id(
await cg.templatable(config[CONF_MESSAGE_ID], args, cg.uint8)
)
)
# TemplatableBytes starts in lambda mode with no function, so a transmit without data sets an empty payload
await automation.templatable_bytes(
config.get(CONF_DATA, []),
args,
var.set_data_template,
var.set_data_static,
"abbwelcome_data",
)
# Mirage
(
MirageData,
MirageBinarySensor,
MirageTrigger,
MirageAction,
MirageDumper,
) = declare_protocol("Mirage")
MIRAGE_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=14, max=14)),
}
)
@register_binary_sensor("mirage", MirageBinarySensor, MIRAGE_SCHEMA)
def mirage_binary_sensor(var, config):
cg.add(var.set_code(config[CONF_CODE]))
@register_trigger("mirage", MirageTrigger, MirageData)
def mirage_trigger(var, config):
pass
@register_dumper("mirage", MirageDumper)
def mirage_dumper(var, config):
pass
@register_action("mirage", MirageAction, MIRAGE_SCHEMA)
async def mirage_action(var, config, args):
await _code_bytes(
config[CONF_CODE], args, var.set_code_template, var.set_code_static
)
# Toto
(
TotoData,
TotoBinarySensor,
TotoTrigger,
_,
TotoDumper,
) = declare_protocol("Toto")
TOTO_SCHEMA = cv.Schema(
{
cv.Optional(CONF_RC_CODE_1, default=0): cv.hex_int_range(0, 0xF),
cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_int_range(0, 0xF),
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
}
)
@register_binary_sensor("toto", TotoBinarySensor, TOTO_SCHEMA)
def toto_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
TotoData,
("rc_code_1", config[CONF_RC_CODE_1]),
("rc_code_2", config[CONF_RC_CODE_2]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("toto", TotoTrigger, TotoData)
def toto_trigger(var, config):
pass
@register_dumper("toto", TotoDumper)
def toto_dumper(var, config):
pass
def _toto_default_repeat(config: ConfigType) -> ConfigType:
# Toto remotes send each frame 3 times, 36 ms apart
if CONF_REPEAT in config:
return config
return {**config, CONF_REPEAT: {CONF_TIMES: 3, CONF_WAIT_TIME: 36000}}
register_protocol_action(
"toto",
"Toto",
TOTO_SCHEMA,
(CONF_RC_CODE_1, "rc_code_1", cg.uint8),
(CONF_RC_CODE_2, "rc_code_2", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
validate=_toto_default_repeat,
)