1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-08 04:43:46 +01:00

🏗 Merge C++ into python codebase (#504)

## Description:

Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97

Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍

Progress:
- Core support (file copy etc): 80%
- Base Abstractions (light, switch): ~50%
- Integrations: ~10%
- Working? Yes, (but only with ported components).

Other refactors:
- Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`)
- Rework coroutine syntax
- Move from `component/platform.py` to `domain/component.py` structure as with HA
- Move all defaults out of C++ and into config validation.
- Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration.
- Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit.

Future work:
- Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block
- Enable loading from `custom_components` folder.

**Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97

**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>

## Checklist:
  - [ ] The code change is tested and works locally.
  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).

If user exposed functionality or configuration variables are added/changed:
  - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
Otto Winter
2019-04-17 12:06:00 +02:00
committed by GitHub
parent 049807e3ab
commit 6682c43dfa
817 changed files with 54156 additions and 10830 deletions

View File

@@ -0,0 +1,658 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import ACTION_REGISTRY
from esphome.components import binary_sensor as binary_sensor_
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \
CONF_COMMAND, CONF_CODE, CONF_PULSE_LENGTH, CONF_SYNC, CONF_ZERO, CONF_ONE, CONF_INVERTED, \
CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \
CONF_WAIT_TIME, CONF_TIMES
from esphome.core import coroutine
from esphome.py_compat import string_types, text_type
from esphome.util import ServiceRegistry
AUTO_LOAD = ['binary_sensor']
CONF_RECEIVER_ID = 'receiver_id'
CONF_TRANSMITTER_ID = 'transmitter_id'
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', cg.Trigger, RemoteReceiverListener)
RemoteTransmitterDumper = ns.class_('RemoteTransmitterDumper')
RemoteTransmitterActionBase = ns.class_('RemoteTransmitterActionBase', cg.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)
@coroutine
def register_listener(var, config):
receiver = yield cg.get_variable(config[CONF_RECEIVER_ID])
cg.add(receiver.register_listener(var))
def register_binary_sensor(name, type, schema):
if not isinstance(schema, cv.Schema):
schema = cv.Schema(schema)
validator = schema.extend({
cv.GenerateID(): cv.declare_variable_id(type),
cv.GenerateID(CONF_RECEIVER_ID): cv.use_variable_id(RemoteReceiverBase),
})
registerer = BINARY_SENSOR_REGISTRY.register(name, validator)
def decorator(func):
@coroutine
def new_func(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield register_listener(var, config)
yield coroutine(func)(var, config)
yield var
return registerer(new_func)
return decorator
def register_trigger(name, type, data_type):
validator = automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(type),
cv.GenerateID(CONF_RECEIVER_ID): cv.use_variable_id(RemoteReceiverBase),
})
registerer = TRIGGER_REGISTRY.register('on_{}'.format(name), validator)
def decorator(func):
@coroutine
def new_func(config):
var = cg.new_Pvariable(config[CONF_TRIGGER_ID])
yield register_listener(var, config)
yield coroutine(func)(var, config)
yield automation.build_automation(var, [(data_type, 'x')], config)
yield var
return registerer(new_func)
return decorator
def register_dumper(name, type):
validator = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(type),
cv.GenerateID(CONF_RECEIVER_ID): cv.use_variable_id(RemoteReceiverBase),
})
registerer = DUMPER_REGISTRY.register(name, validator)
def decorator(func):
@coroutine
def new_func(config):
var = cg.new_Pvariable(config[CONF_ID])
receiver = yield cg.get_variable(config[CONF_RECEIVER_ID])
cg.add(receiver.register_dumper(var))
yield coroutine(func)(var, config)
yield var
return registerer(new_func)
return decorator
def register_action(name, type_, schema):
validator = templatize(schema).extend({
cv.GenerateID(): cv.declare_variable_id(type_),
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_variable_id(RemoteTransmitterBase),
cv.Optional(CONF_REPEAT): cv.Schema({
cv.Required(CONF_TIMES): cv.templatable(cv.positive_int),
cv.Optional(CONF_WAIT_TIME, default='10ms'):
cv.templatable(cv.positive_time_period_milliseconds),
}),
})
registerer = ACTION_REGISTRY.register('remote_transmitter.transmit_{}'.format(name), validator)
def decorator(func):
@coroutine
def new_func(config, action_id, template_arg, args):
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
type = type_.template(template_arg)
var = cg.Pvariable(action_id, type.new(), type=type)
cg.add(var.set_parent(transmitter))
if CONF_REPEAT in config:
conf = config[CONF_REPEAT]
template_ = yield cg.templatable(conf[CONF_TIMES], args, cg.uint32)
cg.add(var.set_send_times(template_))
template_ = yield cg.templatable(conf[CONF_WAIT_TIME], args, cg.uint32)
cg.add(var.set_send_wait(template_))
yield coroutine(func)(var, config, args)
yield var
return registerer(new_func)
return decorator
def declare_protocol(name):
data = ns.struct('{}Data'.format(name))
protocol = ns.class_('{}Protocol'.format(name))
binary_sensor = ns.class_('{}BinarySensor'.format(name), RemoteReceiverBinarySensorBase)
trigger = ns.class_('{}Trigger'.format(name), RemoteReceiverTrigger)
action = ns.class_('{}Action'.format(name), RemoteTransmitterActionBase)
dumper = ns.class_('{}Dumper'.format(name), RemoteTransmitterDumper)
return data, protocol, binary_sensor, trigger, action, dumper
BINARY_SENSOR_REGISTRY = ServiceRegistry()
TRIGGER_REGISTRY = ServiceRegistry()
DUMPER_REGISTRY = ServiceRegistry()
def validate_dumpers(value):
if isinstance(value, string_types) and value.lower() == 'all':
return validate_dumpers(list(DUMPER_REGISTRY.keys()))
return cv.validate_registry('dumper', DUMPER_REGISTRY, [])(value)
def validate_binary_sensor(base_schema):
validator = cv.validate_registry_entry('remote receiver', BINARY_SENSOR_REGISTRY,
cv.extract_keys(base_schema))
return validator
def validate_triggers(base_schema):
assert isinstance(base_schema, cv.Schema)
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)
return new_schema(config)
return validator
@coroutine
def build_binary_sensor(config):
var = yield cg.build_registry_entry(BINARY_SENSOR_REGISTRY, config)
yield var
@coroutine
def build_triggers(full_config):
for key in TRIGGER_REGISTRY:
for config in full_config.get(key, []):
func = TRIGGER_REGISTRY[key][1]
yield func(config)
@coroutine
def build_dumpers(config):
yield cg.build_registry_list(DUMPER_REGISTRY, config)
# JVC
JVCData, JVCProtocol, JVCBinarySensor, JVCTrigger, JVCAction, 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_action('jvc', JVCAction, JVC_SCHEMA)
def jvc_action(var, config, args):
template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint32)
cg.add(var.set_data(template_))
# LG
LGData, LGProtocol, LGBinarySensor, LGTrigger, LGAction, 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_action('lg', LGAction, LG_SCHEMA)
def lg_action(var, config, args):
template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint32)
cg.add(var.set_data(template_))
template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint8)
cg.add(var.set_nbits(template_))
# NEC
NECData, NECProtocol, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol('NEC')
NEC_SCHEMA = cv.Schema({
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Required(CONF_COMMAND): cv.hex_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]),
)))
@register_trigger('nec', NECTrigger, NECData)
def nec_trigger(var, config):
pass
@register_dumper('nec', NECDumper)
def nec_dumper(var, config):
pass
@register_action('nec', NECAction, NEC_SCHEMA)
def nec_action(var, config, args):
template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
cg.add(var.set_address(template_))
template_ = yield cg.templatable(config[CONF_COMMAND], args, cg.uint16)
cg.add(var.set_command(template_))
# Sony
SonyData, SonyProtocol, SonyBinarySensor, SonyTrigger, SonyAction, 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_action('sony', SonyAction, SONY_SCHEMA)
def sony_action(var, config, args):
template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint16)
cg.add(var.set_data(template_))
template_ = yield cg.templatable(config[CONF_NBITS], args, cg.uint32)
cg.add(var.set_nbits(template_))
# 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:
if this_negative == last_negative:
raise cv.Invalid("Values must alternate between being positive and negative, "
"please see index {} and {}".format(i, i + 1), [i])
last_negative = this_negative
return value
RawData, RawProtocol, 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_variable_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_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)
def raw_action(var, config, args):
code_ = config[CONF_CODE]
if cg.is_template(code_):
template_ = yield 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_)))
# RC5
RC5Data, RC5Protocol, RC5BinarySensor, RC5Trigger, RC5Action, 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=0x3F)),
})
@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_action('rc5', RC5Action, RC5_SCHEMA)
def rc5_action(var, config, args):
template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
cg.add(var.set_address(template_))
template_ = yield 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.All(cv.Coerce(int), cv.Range(min=1, max=7)),
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, text_type)):
raise cv.Invalid("All RCSwitch codes must be in quotes ('')")
for c in value:
if c not in ('0', '1'):
raise cv.Invalid(u"Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed"
u"".format(c))
if len(value) > 32:
raise cv.Invalid("Maximum length for RCSwitch codes is 32, code '{}' has length {}"
"".format(value, len(value)))
if not value:
raise cv.Invalid("RCSwitch code must not be empty")
return value
def build_rc_switch_protocol(config):
if isinstance(config, int):
return rc_switch_protocols[config]
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])
RC_SWITCH_RAW_SCHEMA = cv.Schema({
cv.Required(CONF_CODE): validate_rc_switch_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.All(cv.uint8_t, cv.Range(min=1, max=4)),
cv.Required(CONF_CHANNEL): cv.All(cv.uint8_t, cv.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.All(cv.uint8_t, cv.Range(min=1, max=4)),
cv.Required(CONF_DEVICE): cv.All(cv.uint8_t, cv.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.All(cv.uint8_t, cv.Range(min=1, max=3)),
cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
})
rc_switch_protocols = ns.rc_switch_protocols
RCSwitchBase = ns.class_('RCSwitchBase')
RCSwitchDumper = ns.class_('RCSwitchDumper', RemoteTransmitterDumper)
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(build_rc_switch_protocol(config[CONF_PROTOCOL])))
cg.add(var.set_code(config[CONF_CODE]))
@register_action('rc_switch_raw', RCSwitchRawAction, RC_SWITCH_RAW_SCHEMA)
def rc_switch_raw_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol)
cg.add(var.set_protocol(proto))
cg.add(var.set_code((yield 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(build_rc_switch_protocol(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)
def rc_switch_type_a_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol)
cg.add(var.set_protocol(proto))
cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string))))
cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.std_string))))
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, 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(build_rc_switch_protocol(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)
def rc_switch_type_b_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol)
cg.add(var.set_protocol(proto))
cg.add(var.set_address((yield cg.templatable(config[CONF_ADDRESS], args, cg.uint8))))
cg.add(var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8))))
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, 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(build_rc_switch_protocol(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)
def rc_switch_type_c_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol)
cg.add(var.set_protocol(proto))
cg.add(var.set_family((yield cg.templatable(config[CONF_FAMILY], args, cg.std_string))))
cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8))))
cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint8))))
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool))))
@register_binary_sensor('rc_switch_type_d', RCSwitchRawReceiver, RC_SWITCH_TYPE_D_SCHEMA)
def rc_switch_type_d_binary_sensor(var, config):
cg.add(var.set_protocol(build_rc_switch_protocol(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)
def rc_switch_type_d_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol)
cg.add(var.set_protocol(proto))
cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string))))
cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint8))))
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool))))
@register_dumper('rc_switch', RCSwitchDumper)
def rc_switch_dumper(var, config):
pass
# Samsung
(SamsungData, SamsungProtocol, SamsungBinarySensor, SamsungTrigger, SamsungAction,
SamsungDumper) = declare_protocol('Samsung')
SAMSUNG_SCHEMA = cv.Schema({
cv.Required(CONF_DATA): cv.hex_uint32_t,
})
@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]),
)))
@register_trigger('samsung', SamsungTrigger, SamsungData)
def samsung_trigger(var, config):
pass
@register_dumper('samsung', SamsungDumper)
def samsung_dumper(var, config):
pass
@register_action('samsung', SamsungAction, SAMSUNG_SCHEMA)
def samsung_action(var, config, args):
template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint16)
cg.add(var.set_data(template_))
# Panasonic
(PanasonicData, PanasonicProtocol, PanasonicBinarySensor, PanasonicTrigger, PanasonicAction,
PanasonicDumper) = declare_protocol('Panasonic')
PANASONIC_SCHEMA = cv.Schema({
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
cv.Required(CONF_COMMAND): cv.hex_uint32_t,
})
@register_binary_sensor('panasonic', PanasonicBinarySensor, PANASONIC_SCHEMA)
def panasonic_binary_sensor(var, config):
cg.add(var.set_data(cg.StructInitializer(
PanasonicData,
('address', config[CONF_ADDRESS]),
('command', config[CONF_COMMAND]),
)))
@register_trigger('panasonic', PanasonicTrigger, PanasonicData)
def panasonic_trigger(var, config):
pass
@register_dumper('panasonic', PanasonicDumper)
def panasonic_dumper(var, config):
pass
@register_action('panasonic', PanasonicAction, PANASONIC_SCHEMA)
def panasonic_action(var, config, args):
template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
cg.add(var.set_address(template_))
template_ = yield cg.templatable(config[CONF_COMMAND], args, cg.uint32)
cg.add(var.set_command(template_))

View File

@@ -0,0 +1,51 @@
#include "jvc_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.jvc";
static const uint8_t NBITS = 16;
static const uint32_t HEADER_HIGH_US = 8400;
static const uint32_t HEADER_LOW_US = 4200;
static const uint32_t BIT_ONE_LOW_US = 1725;
static const uint32_t BIT_ZERO_LOW_US = 525;
static const uint32_t BIT_HIGH_US = 525;
void JVCProtocol::encode(RemoteTransmitData *dst, const JVCData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + NBITS * 2u);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
if (data.data & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
dst->mark(BIT_HIGH_US);
}
optional<JVCData> JVCProtocol::decode(RemoteReceiveData src) {
JVCData out{.data = 0};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
for (uint8_t i = 0; i < NBITS; i++) {
out.data <<= 1UL;
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.data |= 1UL;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
out.data |= 0UL;
} else {
return {};
}
}
return out;
}
void JVCProtocol::dump(const JVCData &data) { ESP_LOGD(TAG, "Received JVC: data=0x%04X", data.data); }
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct JVCData {
uint32_t data;
bool operator==(const JVCData &rhs) const { return data == rhs.data; }
};
class JVCProtocol : public RemoteProtocol<JVCData> {
public:
void encode(RemoteTransmitData *dst, const JVCData &data) override;
optional<JVCData> decode(RemoteReceiveData src) override;
void dump(const JVCData &data) override;
};
DECLARE_REMOTE_PROTOCOL(JVC)
template<typename... Ts> class JVCAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint32_t, data)
void encode(RemoteTransmitData *dst, Ts... x) override {
JVCData data{};
data.data = this->data_.value(x...);
JVCProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,57 @@
#include "lg_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.lg";
static const uint32_t HEADER_HIGH_US = 8000;
static const uint32_t HEADER_LOW_US = 4000;
static const uint32_t BIT_HIGH_US = 600;
static const uint32_t BIT_ONE_LOW_US = 1600;
static const uint32_t BIT_ZERO_LOW_US = 550;
void LGProtocol::encode(RemoteTransmitData *dst, const LGData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + data.nbits * 2u);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) {
if (data.data & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
dst->mark(BIT_HIGH_US);
}
optional<LGData> LGProtocol::decode(RemoteReceiveData src) {
LGData out{
.data = 0,
.nbits = 0,
};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
for (out.nbits = 0; out.nbits < 32; out.nbits++) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.data = (out.data << 1) | 1;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
out.data = (out.data << 1) | 0;
} else if (out.nbits == 28) {
return out;
} else {
return {};
}
}
return out;
}
void LGProtocol::dump(const LGData &data) {
ESP_LOGD(TAG, "Received LG: data=0x%08X, nbits=%d", data.data, data.nbits);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct LGData {
uint32_t data;
uint8_t nbits;
bool operator==(const LGData &rhs) const { return data == rhs.data && nbits == rhs.nbits; }
};
class LGProtocol : public RemoteProtocol<LGData> {
public:
void encode(RemoteTransmitData *dst, const LGData &data) override;
optional<LGData> decode(RemoteReceiveData src) override;
void dump(const LGData &data) override;
};
DECLARE_REMOTE_PROTOCOL(LG)
template<typename... Ts> class LGAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint32_t, data)
TEMPLATABLE_VALUE(uint8_t, nbits)
void encode(RemoteTransmitData *dst, Ts... x) override {
LGData data{};
data.data = this->data_.value(x...);
data.nbits = this->nbits_.value(x...);
LGProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,72 @@
#include "nec_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.nec";
static const uint32_t HEADER_HIGH_US = 9000;
static const uint32_t HEADER_LOW_US = 4500;
static const uint32_t BIT_HIGH_US = 560;
static const uint32_t BIT_ONE_LOW_US = 1690;
static const uint32_t BIT_ZERO_LOW_US = 560;
void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
dst->reserve(68);
dst->set_carrier_frequency(38000);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
if (data.address & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
if (data.command & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
dst->mark(BIT_HIGH_US);
}
optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
NECData data{
.address = 0,
.command = 0,
};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
data.address |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
data.address &= ~mask;
} else {
return {};
}
}
for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
data.command |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
data.command &= ~mask;
} else {
return {};
}
}
src.expect_mark(BIT_HIGH_US);
return data;
}
void NECProtocol::dump(const NECData &data) {
ESP_LOGD(TAG, "Received NEC: address=0x%04X, command=0x%04X", data.address, data.command);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,37 @@
#pragma once
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct NECData {
uint16_t address;
uint16_t command;
bool operator==(const NECData &rhs) const { return address == rhs.address && command == rhs.command; }
};
class NECProtocol : public RemoteProtocol<NECData> {
public:
void encode(RemoteTransmitData *dst, const NECData &data) override;
optional<NECData> decode(RemoteReceiveData src) override;
void dump(const NECData &data) override;
};
DECLARE_REMOTE_PROTOCOL(NEC)
template<typename... Ts> class NECAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint16_t, address)
TEMPLATABLE_VALUE(uint16_t, command)
void encode(RemoteTransmitData *dst, Ts... x) override {
NECData data{};
data.address = this->address_.value(x...);
data.command = this->command_.value(x...);
NECProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,72 @@
#include "panasonic_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.panasonic";
static const uint32_t HEADER_HIGH_US = 3502;
static const uint32_t HEADER_LOW_US = 1750;
static const uint32_t BIT_HIGH_US = 502;
static const uint32_t BIT_ZERO_LOW_US = 400;
static const uint32_t BIT_ONE_LOW_US = 1244;
void PanasonicProtocol::encode(RemoteTransmitData *dst, const PanasonicData &data) {
dst->reserve(100);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
dst->set_carrier_frequency(35000);
uint32_t mask;
for (mask = 1UL << 15; mask != 0; mask >>= 1) {
if (data.address & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
for (mask = 1UL << 31; mask != 0; mask >>= 1) {
if (data.command & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
dst->mark(BIT_HIGH_US);
}
optional<PanasonicData> PanasonicProtocol::decode(RemoteReceiveData src) {
PanasonicData out{
.address = 0,
.command = 0,
};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
uint32_t mask;
for (mask = 1UL << 15; mask != 0; mask >>= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.address |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
out.address &= ~mask;
} else {
return {};
}
}
for (mask = 1UL << 31; mask != 0; mask >>= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.command |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
out.command &= ~mask;
} else {
return {};
}
}
return out;
}
void PanasonicProtocol::dump(const PanasonicData &data) {
ESP_LOGD(TAG, "Received Panasonic: address=0x%04X, command=0x%08X", data.address, data.command);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct PanasonicData {
uint16_t address;
uint32_t command;
bool operator==(const PanasonicData &rhs) const { return address == rhs.address && command == rhs.command; }
};
class PanasonicProtocol : public RemoteProtocol<PanasonicData> {
public:
void encode(RemoteTransmitData *dst, const PanasonicData &data) override;
optional<PanasonicData> decode(RemoteReceiveData src) override;
void dump(const PanasonicData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Panasonic)
template<typename... Ts> class PanasonicAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint16_t, address)
TEMPLATABLE_VALUE(uint32_t, command)
void encode(RemoteTransmitData *dst, Ts... x) override {
PanasonicData data{};
data.address = this->address_.value(x...);
data.command = this->command_.value(x...);
PanasonicProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,46 @@
#include "raw_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.raw";
void RawDumper::dump(RemoteReceiveData src) {
char buffer[256];
uint32_t buffer_offset = 0;
buffer_offset += sprintf(buffer, "Received Raw: ");
for (int32_t i = 0; i < src.size(); i++) {
const int32_t value = src[i];
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
int written;
if (i + 1 < src.size()) {
written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value);
} else {
written = snprintf(buffer + buffer_offset, remaining_length, "%d", value);
}
if (written < 0 || written >= int(remaining_length)) {
// write failed, flush...
buffer[buffer_offset] = '\0';
ESP_LOGD(TAG, "%s", buffer);
buffer_offset = 0;
written = sprintf(buffer, " ");
if (i + 1 < src.size()) {
written += sprintf(buffer + written, "%d, ", value);
} else {
written += sprintf(buffer + written, "%d", value);
}
}
buffer_offset += written;
}
if (buffer_offset != 0) {
ESP_LOGD(TAG, "%s", buffer);
}
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,75 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
class RawBinarySensor : public RemoteReceiverBinarySensorBase {
public:
bool matches(RemoteReceiveData src) override {
for (size_t i = 0; i < this->len_; i++) {
auto val = this->data_[i];
if (val < 0) {
if (!src.expect_space(static_cast<uint32_t>(-val)))
return false;
} else {
if (!src.expect_mark(static_cast<uint32_t>(val)))
return false;
}
}
return true;
}
void set_data(const int32_t *data) { data_ = data; }
void set_len(size_t len) { len_ = len; }
protected:
const int32_t *data_;
size_t len_;
};
class RawTrigger : public Trigger<std::vector<int32_t>>, public Component, public RemoteReceiverListener {
protected:
bool on_receive(RemoteReceiveData src) override {
this->trigger(*src.get_raw_data());
return false;
}
};
template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts...> {
public:
void set_code_template(std::function<std::vector<int32_t>(Ts...)> func) { this->code_func_ = func; }
void set_code_static(const int32_t *code, size_t len) {
this->code_static_ = code;
this->code_static_len_ = len;
}
void encode(RemoteTransmitData *dst, Ts... x) override {
// dst->set_data(data);
if (this->code_static_ != nullptr) {
for (size_t i = 0; i < this->code_static_len_; i++) {
auto val = this->code_static_[i];
if (val < 0)
dst->space(static_cast<uint32_t>(-val));
else
dst->mark(static_cast<uint32_t>(val));
}
} else {
dst->set_data(this->code_func_(x...));
}
}
protected:
std::function<std::vector<int32_t>(Ts...)> code_func_{};
const int32_t *code_static_{nullptr};
int32_t code_static_len_{0};
};
class RawDumper : public RemoteReceiverDumperBase {
public:
void dump(RemoteReceiveData src) override;
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,62 @@
#include "rc5_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.rc5";
static const uint32_t BIT_TIME_US = 889;
static const uint8_t NBITS = 14;
void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) {
static bool TOGGLE = false;
dst->set_carrier_frequency(36000);
uint64_t out_data = 0;
out_data |= 0b11 << 12;
out_data |= TOGGLE << 11;
out_data |= data.address << 6;
out_data |= data.command;
for (uint64_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
if (out_data & mask) {
dst->space(BIT_TIME_US);
dst->mark(BIT_TIME_US);
} else {
dst->mark(BIT_TIME_US);
dst->space(BIT_TIME_US);
}
}
TOGGLE = !TOGGLE;
}
optional<RC5Data> RC5Protocol::decode(RemoteReceiveData src) {
RC5Data out{
.address = 0,
.command = 0,
};
src.expect_space(BIT_TIME_US);
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US) || !src.expect_mark(BIT_TIME_US))
return {};
uint64_t out_data = 0;
for (int bit = NBITS - 3; bit >= 0; bit--) {
if (src.expect_space(BIT_TIME_US) && src.expect_mark(BIT_TIME_US)) {
out_data |= 1 << bit;
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
out_data |= 0 << bit;
} else {
return {};
}
}
out.command = out_data & 0x3F;
out.address = (out_data >> 6) & 0x1F;
return out;
}
void RC5Protocol::dump(const RC5Data &data) {
ESP_LOGD(TAG, "Received RC5: address=0x%02X, command=0x%02X", data.address, data.command);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct RC5Data {
uint8_t address;
uint8_t command;
bool operator==(const RC5Data &rhs) const { return address == rhs.address && command == rhs.command; }
};
class RC5Protocol : public RemoteProtocol<RC5Data> {
public:
void encode(RemoteTransmitData *dst, const RC5Data &data) override;
optional<RC5Data> decode(RemoteReceiveData src) override;
void dump(const RC5Data &data) override;
};
DECLARE_REMOTE_PROTOCOL(RC5)
template<typename... Ts> class RC5Action : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint8_t, address)
TEMPLATABLE_VALUE(uint8_t, command)
void encode(RemoteTransmitData *dst, Ts... x) override {
RC5Data data{};
data.address = this->address_.value(x...);
data.command = this->command_.value(x...);
RC5Protocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,245 @@
#include "rc_switch_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.rc_switch";
RCSwitchBase rc_switch_protocols[8] = {RCSwitchBase(0, 0, 0, 0, 0, 0, false),
RCSwitchBase(350, 10850, 350, 1050, 1050, 350, false),
RCSwitchBase(650, 6500, 650, 1300, 1300, 650, false),
RCSwitchBase(3000, 7100, 400, 1100, 900, 600, false),
RCSwitchBase(380, 2280, 380, 1140, 1140, 380, false),
RCSwitchBase(3000, 7000, 500, 1000, 1000, 500, false),
RCSwitchBase(10350, 450, 450, 900, 900, 450, true),
RCSwitchBase(300, 9300, 150, 900, 900, 150, false)};
RCSwitchBase::RCSwitchBase(uint32_t sync_high, uint32_t sync_low, uint32_t zero_high, uint32_t zero_low,
uint32_t one_high, uint32_t one_low, bool inverted)
: sync_high_(sync_high),
sync_low_(sync_low),
zero_high_(zero_high),
zero_low_(zero_low),
one_high_(one_high),
one_low_(one_low),
inverted_(inverted) {}
void RCSwitchBase::one(RemoteTransmitData *dst) const {
if (!this->inverted_) {
dst->mark(this->one_high_);
dst->space(this->one_low_);
} else {
dst->space(this->one_high_);
dst->mark(this->one_low_);
}
}
void RCSwitchBase::zero(RemoteTransmitData *dst) const {
if (!this->inverted_) {
dst->mark(this->zero_high_);
dst->space(this->zero_low_);
} else {
dst->space(this->zero_high_);
dst->mark(this->zero_low_);
}
}
void RCSwitchBase::sync(RemoteTransmitData *dst) const {
if (!this->inverted_) {
dst->mark(this->sync_high_);
dst->space(this->sync_low_);
} else {
dst->space(this->sync_high_);
dst->mark(this->sync_low_);
}
}
void RCSwitchBase::transmit(RemoteTransmitData *dst, uint32_t code, uint8_t len) const {
for (int16_t i = len - 1; i >= 0; i--) {
if (code & (1 << i))
this->one(dst);
else
this->zero(dst);
}
this->sync(dst);
}
bool RCSwitchBase::expect_one(RemoteReceiveData &src) const {
if (!this->inverted_) {
if (!src.peek_mark(this->one_high_))
return false;
if (!src.peek_space(this->one_low_, 1))
return false;
} else {
if (!src.peek_space(this->one_high_))
return false;
if (!src.peek_mark(this->one_low_, 1))
return false;
}
src.advance(2);
return true;
}
bool RCSwitchBase::expect_zero(RemoteReceiveData &src) const {
if (!this->inverted_) {
if (!src.peek_mark(this->zero_high_))
return false;
if (!src.peek_space(this->zero_low_, 1))
return false;
} else {
if (!src.peek_space(this->zero_high_))
return false;
if (!src.peek_mark(this->zero_low_, 1))
return false;
}
src.advance(2);
return true;
}
bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const {
if (!this->inverted_) {
if (!src.peek_mark(this->sync_high_))
return false;
if (!src.peek_space(this->sync_low_, 1))
return false;
} else {
if (!src.peek_space(this->sync_high_))
return false;
if (!src.peek_mark(this->sync_low_, 1))
return false;
}
src.advance(2);
return true;
}
bool RCSwitchBase::decode(RemoteReceiveData &src, uint32_t *out_data, uint8_t *out_nbits) const {
// ignore if sync doesn't exist
this->expect_sync(src);
*out_data = 0;
for (*out_nbits = 1; *out_nbits < 32; *out_nbits += 1) {
if (this->expect_zero(src)) {
*out_data <<= 1;
*out_data |= 0;
} else if (this->expect_one(src)) {
*out_data <<= 1;
*out_data |= 1;
} else {
*out_nbits -= 1;
return *out_nbits >= 8;
}
}
return true;
}
void RCSwitchBase::simple_code_to_tristate(uint16_t code, uint8_t nbits, uint32_t *out_code) {
*out_code = 0;
for (int8_t i = nbits - 1; i >= 0; i--) {
*out_code <<= 2;
if (code & (1 << i))
*out_code |= 0b01;
else
*out_code |= 0b00;
}
}
void RCSwitchBase::type_a_code(uint8_t switch_group, uint8_t switch_device, bool state, uint32_t *out_code,
uint8_t *out_nbits) {
uint16_t code = 0;
code |= (switch_group & 0b0001) ? 0 : 0b1000;
code |= (switch_group & 0b0010) ? 0 : 0b0100;
code |= (switch_group & 0b0100) ? 0 : 0b0010;
code |= (switch_group & 0b1000) ? 0 : 0b0001;
code <<= 4;
code |= (switch_device & 0b0001) ? 0 : 0b1000;
code |= (switch_device & 0b0010) ? 0 : 0b0100;
code |= (switch_device & 0b0100) ? 0 : 0b0010;
code |= (switch_device & 0b1000) ? 0 : 0b0001;
code <<= 2;
code |= state ? 0b01 : 0b10;
simple_code_to_tristate(code, 10, out_code);
*out_nbits = 20;
}
void RCSwitchBase::type_b_code(uint8_t address_code, uint8_t channel_code, bool state, uint32_t *out_code,
uint8_t *out_nbits) {
uint16_t code = 0;
code |= (address_code == 1) ? 0 : 0b1000;
code |= (address_code == 2) ? 0 : 0b0100;
code |= (address_code == 3) ? 0 : 0b0010;
code |= (address_code == 4) ? 0 : 0b0001;
code <<= 4;
code |= (channel_code == 1) ? 0 : 0b1000;
code |= (channel_code == 2) ? 0 : 0b0100;
code |= (channel_code == 3) ? 0 : 0b0010;
code |= (channel_code == 4) ? 0 : 0b0001;
code <<= 4;
code |= 0b1110;
code |= state ? 0b1 : 0b0;
simple_code_to_tristate(code, 12, out_code);
*out_nbits = 24;
}
void RCSwitchBase::type_c_code(uint8_t family, uint8_t group, uint8_t device, bool state, uint32_t *out_code,
uint8_t *out_nbits) {
uint16_t code = 0;
code |= (family & 0b0001) ? 0b1000 : 0;
code |= (family & 0b0010) ? 0b0100 : 0;
code |= (family & 0b0100) ? 0b0010 : 0;
code |= (family & 0b1000) ? 0b0001 : 0;
code <<= 4;
code |= ((device - 1) & 0b01) ? 0b1000 : 0;
code |= ((device - 1) & 0b10) ? 0b0100 : 0;
code |= ((group - 1) & 0b01) ? 0b0010 : 0;
code |= ((group - 1) & 0b10) ? 0b0001 : 0;
code <<= 4;
code |= 0b0110;
code |= state ? 0b1 : 0b0;
simple_code_to_tristate(code, 12, out_code);
*out_nbits = 24;
}
void RCSwitchBase::type_d_code(uint8_t group, uint8_t device, bool state, uint32_t *out_code, uint8_t *out_nbits) {
*out_code = 0;
*out_code |= (group == 0) ? 0b11000000 : 0b01000000;
*out_code |= (group == 1) ? 0b00110000 : 0b00010000;
*out_code |= (group == 2) ? 0b00001100 : 0b00000100;
*out_code |= (group == 3) ? 0b00000011 : 0b00000001;
*out_code <<= 6;
*out_code |= (device == 1) ? 0b110000 : 0b010000;
*out_code |= (device == 2) ? 0b001100 : 0b000100;
*out_code |= (device == 3) ? 0b000011 : 0b000001;
*out_code <<= 6;
*out_code |= 0b000000;
*out_code <<= 4;
*out_code |= state ? 0b1100 : 0b0011;
*out_nbits = 24;
}
uint32_t decode_binary_string(const std::string &data) {
uint32_t ret = 0;
for (char c : data) {
ret <<= 1UL;
ret |= (c != '0');
}
return ret;
}
bool RCSwitchRawReceiver::matches(RemoteReceiveData src) {
uint32_t decoded_code;
uint8_t decoded_nbits;
if (!this->protocol_.decode(src, &decoded_code, &decoded_nbits))
return false;
return decoded_nbits == this->nbits_ && decoded_code == this->code_;
}
void RCSwitchDumper::dump(RemoteReceiveData src) {
for (uint8_t i = 1; i <= 7; i++) {
src.reset();
uint32_t out_data;
uint8_t out_nbits;
RCSwitchBase *protocol = &rc_switch_protocols[i];
if (protocol->decode(src, &out_data, &out_nbits)) {
char buffer[32];
for (uint8_t j = 0; j < out_nbits; j++)
buffer[j] = (out_data & (1 << (out_nbits - j - 1))) ? '1' : '0';
buffer[out_nbits] = '\0';
ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer);
}
}
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,204 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
class RCSwitchBase {
public:
RCSwitchBase() = default;
RCSwitchBase(uint32_t sync_high, uint32_t sync_low, uint32_t zero_high, uint32_t zero_low, uint32_t one_high,
uint32_t one_low, bool inverted);
void one(RemoteTransmitData *dst) const;
void zero(RemoteTransmitData *dst) const;
void sync(RemoteTransmitData *dst) const;
void transmit(RemoteTransmitData *dst, uint32_t code, uint8_t len) const;
bool expect_one(RemoteReceiveData &src) const;
bool expect_zero(RemoteReceiveData &src) const;
bool expect_sync(RemoteReceiveData &src) const;
bool decode(RemoteReceiveData &src, uint32_t *out_data, uint8_t *out_nbits) const;
static void simple_code_to_tristate(uint16_t code, uint8_t nbits, uint32_t *out_code);
static void type_a_code(uint8_t switch_group, uint8_t switch_device, bool state, uint32_t *out_code,
uint8_t *out_nbits);
static void type_b_code(uint8_t address_code, uint8_t channel_code, bool state, uint32_t *out_code,
uint8_t *out_nbits);
static void type_c_code(uint8_t family, uint8_t group, uint8_t device, bool state, uint32_t *out_code,
uint8_t *out_nbits);
static void type_d_code(uint8_t group, uint8_t device, bool state, uint32_t *out_code, uint8_t *out_nbits);
protected:
uint32_t sync_high_{};
uint32_t sync_low_{};
uint32_t zero_high_{};
uint32_t zero_low_{};
uint32_t one_high_{};
uint32_t one_low_{};
bool inverted_{};
};
extern RCSwitchBase rc_switch_protocols[8];
uint32_t decode_binary_string(const std::string &data);
template<typename... Ts> class RCSwitchRawAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(RCSwitchBase, protocol);
TEMPLATABLE_VALUE(std::string, code);
void encode(RemoteTransmitData *dst, Ts... x) override {
auto code = this->code_.value(x...);
uint32_t the_code = decode_binary_string(code);
uint8_t nbits = code.size();
auto proto = this->protocol_.value(x...);
proto.transmit(dst, the_code, nbits);
}
};
template<typename... Ts> class RCSwitchTypeAAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(RCSwitchBase, protocol);
TEMPLATABLE_VALUE(std::string, group);
TEMPLATABLE_VALUE(std::string, device);
TEMPLATABLE_VALUE(bool, state);
void encode(RemoteTransmitData *dst, Ts... x) override {
auto group = this->group_.value(x...);
auto device = this->device_.value(x...);
auto state = this->state_.value(x...);
uint8_t u_group = decode_binary_string(group);
uint8_t u_device = decode_binary_string(device);
uint32_t code;
uint8_t nbits;
RCSwitchBase::type_a_code(u_group, u_device, state, &code, &nbits);
auto proto = this->protocol_.value(x...);
proto.transmit(dst, code, nbits);
}
};
template<typename... Ts> class RCSwitchTypeBAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(RCSwitchBase, protocol);
TEMPLATABLE_VALUE(uint8_t, address);
TEMPLATABLE_VALUE(uint8_t, channel);
TEMPLATABLE_VALUE(bool, state);
void encode(RemoteTransmitData *dst, Ts... x) override {
auto address = this->address_.value(x...);
auto channel = this->channel_.value(x...);
auto state = this->state_.value(x...);
uint32_t code;
uint8_t nbits;
RCSwitchBase::type_b_code(address, channel, state, &code, &nbits);
auto proto = this->protocol_.value(x...);
proto.transmit(dst, code, nbits);
}
};
template<typename... Ts> class RCSwitchTypeCAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(RCSwitchBase, protocol);
TEMPLATABLE_VALUE(std::string, family);
TEMPLATABLE_VALUE(uint8_t, group);
TEMPLATABLE_VALUE(uint8_t, device);
TEMPLATABLE_VALUE(bool, state);
void encode(RemoteTransmitData *dst, Ts... x) override {
auto family = this->family_.value(x...);
auto group = this->group_.value(x...);
auto device = this->device_.value(x...);
auto state = this->state_.value(x...);
auto u_family = static_cast<uint8_t>(tolower(family[0]) - 'a');
uint32_t code;
uint8_t nbits;
RCSwitchBase::type_c_code(u_family, group, device, state, &code, &nbits);
auto proto = this->protocol_.value(x...);
proto.transmit(dst, code, nbits);
}
};
template<typename... Ts> class RCSwitchTypeDAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(RCSwitchBase, protocol);
TEMPLATABLE_VALUE(std::string, group);
TEMPLATABLE_VALUE(uint8_t, device);
TEMPLATABLE_VALUE(bool, state);
void encode(RemoteTransmitData *dst, Ts... x) override {
auto group = this->group_.value(x...);
auto device = this->device_.value(x...);
auto state = this->state_.value(x...);
auto u_group = static_cast<uint8_t>(tolower(group[0]) - 'a');
uint32_t code;
uint8_t nbits;
RCSwitchBase::type_d_code(u_group, device, state, &code, &nbits);
auto proto = this->protocol_.value(x...);
proto.transmit(dst, code, nbits);
}
};
class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase {
public:
void set_protocol(const RCSwitchBase &a_protocol) { this->protocol_ = a_protocol; }
void set_code(uint32_t code) { this->code_ = code; }
void set_code(const std::string &code) {
this->code_ = decode_binary_string(code);
this->nbits_ = code.size();
}
void set_nbits(uint8_t nbits) { this->nbits_ = nbits; }
void set_type_a(const std::string &group, const std::string &device, bool state) {
uint8_t u_group = decode_binary_string(group);
uint8_t u_device = decode_binary_string(device);
RCSwitchBase::type_a_code(u_group, u_device, state, &this->code_, &this->nbits_);
}
void set_type_b(uint8_t address_code, uint8_t channel_code, bool state) {
RCSwitchBase::type_b_code(address_code, channel_code, state, &this->code_, &this->nbits_);
}
void set_type_c(std::string family, uint8_t group, uint8_t device, bool state) {
auto u_family = static_cast<uint8_t>(tolower(family[0]) - 'a');
RCSwitchBase::type_c_code(u_family, group, device, state, &this->code_, &this->nbits_);
}
void set_type_d(std::string group, uint8_t device, bool state) {
auto u_group = static_cast<uint8_t>(tolower(group[0]) - 'a');
RCSwitchBase::type_d_code(u_group, device, state, &this->code_, &this->nbits_);
}
protected:
bool matches(RemoteReceiveData src) override;
RCSwitchBase protocol_;
uint32_t code_;
uint8_t nbits_;
};
class RCSwitchDumper : public RemoteReceiverDumperBase {
public:
void dump(RemoteReceiveData src) override;
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,59 @@
#include "remote_base.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote_base";
RemoteComponentBase::RemoteComponentBase(GPIOPin *pin) : pin_(pin) {
#ifdef ARDUINO_ARCH_ESP32
static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0;
this->channel_ = next_rmt_channel;
next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + 1);
#endif
}
void RemoteReceiverBinarySensorBase::dump_config() { LOG_BINARY_SENSOR("", "Remote Receiver Binary Sensor", this); }
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
const std::vector<int32_t> &vec = this->temp_.get_data();
char buffer[256];
uint32_t buffer_offset = 0;
buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait);
for (int32_t i = 0; i < vec.size(); i++) {
const int32_t value = vec[i];
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
int written;
if (i + 1 < vec.size()) {
written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value);
} else {
written = snprintf(buffer + buffer_offset, remaining_length, "%d", value);
}
if (written < 0 || written >= int(remaining_length)) {
// write failed, flush...
buffer[buffer_offset] = '\0';
ESP_LOGVV(TAG, "%s", buffer);
buffer_offset = 0;
written = sprintf(buffer, " ");
if (i + 1 < vec.size()) {
written += sprintf(buffer + written, "%d, ", value);
} else {
written += sprintf(buffer + written, "%d", value);
}
}
buffer_offset += written;
}
if (buffer_offset != 0) {
ESP_LOGVV(TAG, "%s", buffer);
}
#endif
this->send_internal(send_times, send_wait);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,343 @@
#include <utility>
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/core/automation.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#ifdef ARDUINO_ARCH_ESP32
#include <driver/rmt.h>
#endif
namespace esphome {
namespace remote_base {
class RemoteTransmitData {
public:
void mark(uint32_t length) { this->data_.push_back(length); }
void space(uint32_t length) { this->data_.push_back(-length); }
void item(uint32_t mark, uint32_t space) {
this->mark(mark);
this->space(space);
}
void reserve(uint32_t len) { this->data_.reserve(len); }
void set_carrier_frequency(uint32_t carrier_frequency) { this->carrier_frequency_ = carrier_frequency; }
uint32_t get_carrier_frequency() const { return this->carrier_frequency_; }
const std::vector<int32_t> &get_data() const { return this->data_; }
void set_data(std::vector<int32_t> data) {
this->data_.clear();
this->data_.reserve(data.size());
for (auto dat : data)
this->data_.push_back(dat);
}
void reset() {
this->data_.clear();
this->carrier_frequency_ = 0;
}
std::vector<int32_t>::iterator begin() { return this->data_.begin(); }
std::vector<int32_t>::iterator end() { return this->data_.end(); }
protected:
std::vector<int32_t> data_{};
uint32_t carrier_frequency_{0};
};
class RemoteReceiveData {
public:
RemoteReceiveData(std::vector<int32_t> *data, uint8_t tolerance) : data_(data), tolerance_(tolerance) {}
bool peek_mark(uint32_t length, uint32_t offset = 0) {
if (int32_t(this->index_ + offset) >= this->size())
return false;
int32_t value = this->peek(offset);
const int32_t lo = this->lower_bound_(length);
const int32_t hi = this->upper_bound_(length);
return value >= 0 && lo <= value && value <= hi;
}
bool peek_space(uint32_t length, uint32_t offset = 0) {
if (int32_t(this->index_ + offset) >= this->size())
return false;
int32_t value = this->peek(offset);
const int32_t lo = this->lower_bound_(length);
const int32_t hi = this->upper_bound_(length);
return value <= 0 && lo <= -value && -value <= hi;
}
bool peek_space_at_least(uint32_t length, uint32_t offset = 0) {
if (int32_t(this->index_ + offset) >= this->size())
return false;
int32_t value = this->pos(this->index_ + offset);
const int32_t lo = this->lower_bound_(length);
return value <= 0 && lo <= -value;
}
bool peek_item(uint32_t mark, uint32_t space, uint32_t offset = 0) {
return this->peek_mark(mark, offset) && this->peek_space(space, offset + 1);
}
int32_t peek(uint32_t offset = 0) { return (*this)[this->index_ + offset]; }
void advance(uint32_t amount = 1) { this->index_ += amount; }
bool expect_mark(uint32_t length) {
if (this->peek_mark(length)) {
this->advance();
return true;
}
return false;
}
bool expect_space(uint32_t length) {
if (this->peek_space(length)) {
this->advance();
return true;
}
return false;
}
bool expect_item(uint32_t mark, uint32_t space) {
if (this->peek_item(mark, space)) {
this->advance(2);
return true;
}
return false;
}
void reset() { this->index_ = 0; }
int32_t pos(uint32_t index) const { return (*this->data_)[index]; }
int32_t operator[](uint32_t index) const { return this->pos(index); }
int32_t size() const { return this->data_->size(); }
std::vector<int32_t> *get_raw_data() { return this->data_; }
protected:
int32_t lower_bound_(uint32_t length) { return int32_t(100 - this->tolerance_) * length / 100U; }
int32_t upper_bound_(uint32_t length) { return int32_t(100 + this->tolerance_) * length / 100U; }
uint32_t index_{0};
std::vector<int32_t> *data_;
uint8_t tolerance_;
};
template<typename T> class RemoteProtocol {
public:
virtual void encode(RemoteTransmitData *dst, const T &data) = 0;
virtual optional<T> decode(RemoteReceiveData src) = 0;
virtual void dump(const T &data) = 0;
};
class RemoteComponentBase {
public:
explicit RemoteComponentBase(GPIOPin *pin);
#ifdef ARDUINO_ARCH_ESP32
void set_channel(rmt_channel_t channel) { this->channel_ = channel; }
void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; }
#endif
protected:
#ifdef ARDUINO_ARCH_ESP32
uint32_t from_microseconds(uint32_t us) {
const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u;
return us * ticks_per_ten_us / 10;
}
uint32_t to_microseconds(uint32_t ticks) {
const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u;
return (ticks * 10) / ticks_per_ten_us;
}
#endif
GPIOPin *pin_;
#ifdef ARDUINO_ARCH_ESP32
rmt_channel_t channel_{RMT_CHANNEL_0};
uint8_t clock_divider_{80};
esp_err_t error_code_{ESP_OK};
#endif
};
class RemoteTransmitterBase : public RemoteComponentBase {
public:
RemoteTransmitterBase(GPIOPin *pin) : RemoteComponentBase(pin) {}
class TransmitCall {
public:
explicit TransmitCall(RemoteTransmitterBase *parent) : parent_(parent) {}
RemoteTransmitData *get_data() { return &this->parent_->temp_; }
void set_send_times(uint32_t send_times) { send_times_ = send_times; }
void set_send_wait(uint32_t send_wait) { send_wait_ = send_wait; }
void perform() { this->parent_->send_(this->send_times_, this->send_wait_); }
protected:
RemoteTransmitterBase *parent_;
uint32_t send_times_{1};
uint32_t send_wait_{0};
};
TransmitCall transmit() {
this->temp_.reset();
return TransmitCall(this);
}
protected:
void send_(uint32_t send_times, uint32_t send_wait);
virtual void send_internal(uint32_t send_times, uint32_t send_wait) = 0;
void send_single_() { this->send_(1, 0); }
/// Use same vector for all transmits, avoids many allocations
RemoteTransmitData temp_;
};
class RemoteReceiverListener {
public:
virtual bool on_receive(RemoteReceiveData data) = 0;
};
class RemoteReceiverDumperBase {
public:
virtual void dump(RemoteReceiveData src) = 0;
};
class RemoteReceiverBase : public RemoteComponentBase {
public:
RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {}
void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); }
void register_dumper(RemoteReceiverDumperBase *dumper) { this->dumpers_.push_back(dumper); }
void set_tolerance(uint8_t tolerance) { tolerance_ = tolerance; }
protected:
bool call_listeners_() {
bool success = false;
for (auto *listener : this->listeners_) {
auto data = RemoteReceiveData(&this->temp_, this->tolerance_);
if (listener->on_receive(data))
success = true;
}
return success;
}
void call_dumpers_() {
for (auto *dumper : this->dumpers_) {
auto data = RemoteReceiveData(&this->temp_, this->tolerance_);
dumper->dump(data);
}
}
void call_listeners_dumpers_() {
if (this->call_listeners_())
return;
// If a listener handled, then do not dump
this->call_dumpers_();
}
std::vector<RemoteReceiverListener *> listeners_;
std::vector<RemoteReceiverDumperBase *> dumpers_;
std::vector<int32_t> temp_;
uint8_t tolerance_{25};
};
class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensor,
public Component,
public RemoteReceiverListener {
public:
explicit RemoteReceiverBinarySensorBase() : BinarySensor() {}
void dump_config() override;
virtual bool matches(RemoteReceiveData src) = 0;
bool on_receive(RemoteReceiveData src) override {
if (this->matches(src)) {
this->publish_state(true);
yield();
this->publish_state(false);
return true;
}
return false;
}
};
template<typename T, typename D> class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase {
public:
RemoteReceiverBinarySensor() : RemoteReceiverBinarySensorBase() {}
protected:
bool matches(RemoteReceiveData src) override {
auto proto = T();
auto res = proto.decode(src);
return res.has_value();
}
public:
void set_data(D data) { data_ = data; }
protected:
D data_;
};
template<typename T, typename D> class RemoteReceiverTrigger : public Trigger<D>, public RemoteReceiverListener {
protected:
bool on_receive(RemoteReceiveData src) override {
auto proto = T();
auto res = proto.decode(src);
if (res.has_value()) {
this->trigger(*res);
return true;
}
return false;
}
};
template<typename... Ts> class RemoteTransmitterActionBase : public Action<Ts...> {
public:
void set_parent(RemoteTransmitterBase *parent) { this->parent_ = parent; }
void play(Ts... x) override {
auto call = this->parent_->transmit();
this->encode(call.get_data(), x...);
call.set_send_times(this->send_times_.value_or(x..., 1));
call.set_send_wait(this->send_wait_.value_or(x..., 0));
call.perform();
this->play_next(x...);
}
virtual void encode(RemoteTransmitData *dst, Ts... x) = 0;
TEMPLATABLE_VALUE(uint32_t, send_times);
TEMPLATABLE_VALUE(uint32_t, send_wait);
protected:
RemoteTransmitterBase *parent_{};
};
template<typename T, typename D> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
public:
void dump(RemoteReceiveData src) override {
auto proto = T();
auto decoded = proto.decode(src);
if (!decoded.has_value())
return;
proto.dump(*decoded);
}
};
#define DECLARE_REMOTE_PROTOCOL_(prefix) \
using prefix##BinarySensor = RemoteReceiverBinarySensor<prefix##Protocol, prefix##Data>; \
using prefix##Trigger = RemoteReceiverTrigger<prefix##Protocol, prefix##Data>; \
using prefix##Dumper = RemoteReceiverDumper<prefix##Protocol, prefix##Data>;
#define DECLARE_REMOTE_PROTOCOL(prefix) DECLARE_REMOTE_PROTOCOL_(prefix)
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,58 @@
#include "samsung_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.samsung";
static const uint8_t NBITS = 32;
static const uint32_t HEADER_HIGH_US = 4500;
static const uint32_t HEADER_LOW_US = 4500;
static const uint32_t BIT_HIGH_US = 560;
static const uint32_t BIT_ONE_LOW_US = 1690;
static const uint32_t BIT_ZERO_LOW_US = 560;
static const uint32_t FOOTER_HIGH_US = 560;
static const uint32_t FOOTER_LOW_US = 560;
void SamsungProtocol::encode(RemoteTransmitData *dst, const SamsungData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(4 + NBITS * 2u);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
if (data.data & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
dst->item(FOOTER_HIGH_US, FOOTER_LOW_US);
}
optional<SamsungData> SamsungProtocol::decode(RemoteReceiveData src) {
SamsungData out{
.data = 0,
};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
for (uint8_t i = 0; i < NBITS; i++) {
out.data <<= 1UL;
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.data |= 1UL;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
out.data |= 0UL;
} else {
return {};
}
}
if (!src.expect_mark(FOOTER_HIGH_US))
return {};
return out;
}
void SamsungProtocol::dump(const SamsungData &data) { ESP_LOGD(TAG, "Received Samsung: data=0x%08X", data.data); }
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,35 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct SamsungData {
uint32_t data;
bool operator==(const SamsungData &rhs) const { return data == rhs.data; }
};
class SamsungProtocol : public RemoteProtocol<SamsungData> {
public:
void encode(RemoteTransmitData *dst, const SamsungData &data) override;
optional<SamsungData> decode(RemoteReceiveData src) override;
void dump(const SamsungData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Samsung)
template<typename... Ts> class SamsungAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint32_t, data)
void encode(RemoteTransmitData *dst, Ts... x) override {
SamsungData data{};
data.data = this->data_.value(x...);
SamsungProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,68 @@
#include "sony_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *TAG = "remote.sony";
static const uint32_t HEADER_HIGH_US = 2400;
static const uint32_t HEADER_LOW_US = 600;
static const uint32_t BIT_ONE_HIGH_US = 1200;
static const uint32_t BIT_ZERO_HIGH_US = 600;
static const uint32_t BIT_LOW_US = 600;
void SonyProtocol::encode(RemoteTransmitData *dst, const SonyData &data) {
dst->set_carrier_frequency(40000);
dst->reserve(2 + data.nbits * 2u);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) {
if (data.data & mask)
dst->item(BIT_ONE_HIGH_US, BIT_LOW_US);
else
dst->item(BIT_ZERO_HIGH_US, BIT_LOW_US);
}
}
optional<SonyData> SonyProtocol::decode(RemoteReceiveData src) {
SonyData out{
.data = 0,
.nbits = 0,
};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return out;
for (; out.nbits < 20; out.nbits++) {
uint32_t bit;
if (src.expect_mark(BIT_ONE_HIGH_US)) {
bit = 1;
} else if (src.expect_mark(BIT_ZERO_HIGH_US)) {
bit = 0;
} else if (out.nbits == 12 || out.nbits == 15) {
return out;
} else {
return {};
}
out.data = (out.data << 1UL) | bit;
if (src.expect_space(BIT_LOW_US)) {
// nothing needs to be done
} else if (src.peek_space_at_least(BIT_LOW_US)) {
out.nbits += 1;
if (out.nbits == 12 || out.nbits == 15 || out.nbits == 20)
return out;
return {};
} else {
return {};
}
}
return out;
}
void SonyProtocol::dump(const SonyData &data) {
ESP_LOGD(TAG, "Received Sony: data=0x%08X, nbits=%d", data.data, data.nbits);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct SonyData {
uint32_t data;
uint8_t nbits;
bool operator==(const SonyData &rhs) const { return data == rhs.data && nbits == rhs.nbits; }
};
class SonyProtocol : public RemoteProtocol<SonyData> {
public:
void encode(RemoteTransmitData *dst, const SonyData &data) override;
optional<SonyData> decode(RemoteReceiveData src) override;
void dump(const SonyData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Sony)
template<typename... Ts> class SonyAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint32_t, data)
TEMPLATABLE_VALUE(uint8_t, nbits)
void encode(RemoteTransmitData *dst, Ts... x) override {
SonyData data{};
data.data = this->data_.value(x...);
data.nbits = this->nbits_.value(x...);
SonyProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome