Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/byte_bus/* @clydebarrow
esphome/components/byte_bus/* @clydebarrow @nielsnl68
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter
Expand Down
130 changes: 129 additions & 1 deletion esphome/components/byte_bus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,134 @@
import types
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv

CODEOWNERS = ["@clydebarrow"]
from esphome.schema_extractors import schema_extractor_registry

from esphome.const import (
CONF_ID,
)

CODEOWNERS = ["@clydebarrow", "@nielsnl68"]

CONF_BUS_TYPE = "bus_type"

byte_bus_ns = cg.esphome_ns.namespace("byte_bus")
ByteBus = byte_bus_ns.class_("ByteBus")


class DatabusEntry:
def __init__(self, name, fun, type_id, schema, final=None, final_args=None):
self.name = name
self.fun = fun
self.type_id = type_id
self.raw_schema = schema
self.final = final
self.final_args = final_args

@property
def coroutine_fun(self):
from esphome.core import coroutine

return coroutine(self.fun)

@property
def schema(self):
from esphome.config_validation import Schema

return Schema(self.raw_schema)


class Registry(dict[str, DatabusEntry]):
def __init__(self, base_schema=None, type_id_key=None):
super().__init__()
self.base_schema = base_schema or {}
self.type_id_key = type_id_key

def register(self, name, type_id, schema, final=None, final_args=None):
def decorator(fun):
self[name] = DatabusEntry(name, fun, type_id, schema, final, final_args)
return fun

return decorator


DATABUS_REGISTRY = Registry()


def include_databus(name, *, bus_class, schema, final_validate=None, final_args=None):
result = DATABUS_REGISTRY.register(
name, bus_class, schema, final_validate, final_args
)
return result


def validate_databus_registry(base_schema, **kwargs):
default_schema_option = kwargs.pop("default", None)
base_schema = cv.ensure_schema(base_schema).extend(
{
cv.Optional(CONF_BUS_TYPE): cv.valid,
},
extra=cv.ALLOW_EXTRA,
)

@schema_extractor_registry(DATABUS_REGISTRY)
def validator(value):

default = (
default_schema_option
if not callable(default_schema_option)
else default_schema_option(value)
)

if not isinstance(value, dict):
raise cv.Invalid("This value must be dict !!")
value = value.copy()

key = value.pop(CONF_BUS_TYPE, default)
if key is None:
raise cv.Invalid(f"{CONF_BUS_TYPE} not specified!")

models = cv.extract_keys(DATABUS_REGISTRY)
key_validator = cv.one_of(*models, **kwargs)
key_v = key_validator(key)

if not isinstance(base_schema, cv.Schema):
raise cv.Invalid("base_schema must be a schema !!")

new_schema = base_schema.extend(DATABUS_REGISTRY[key_v].raw_schema)

value = new_schema(value)
value[CONF_BUS_TYPE] = key_v
return value

return validator


async def register_databus(config):
databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]]
rhs = databus.type_id.new()
bus = config[CONF_ID].copy()
bus.id = bus.id + "_bus"
var = cg.Pvariable(bus, rhs, databus.type_id)
databus.bus_id = bus.id
return await databus.fun(config, var, databus)


def get_config_from_id(value):
fconf = fv.full_config.get()
path = fconf.get_path_for_id(value)[:-1]
return fconf.get_config_for_path(path)


def final_validate_databus_schema(name: str):
def validate(config):
databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]]

if isinstance(databus.final, types.FunctionType):
kargs = {} if databus.final_args is None else databus.final_args
kargs["config"] = config
return databus.final(name, **kargs)
return config

return validate
23 changes: 20 additions & 3 deletions esphome/components/byte_bus/byte_bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,25 @@ class ByteBus {
* @param data
* @param length
*/
virtual void write_cmd_data(int cmd, const uint8_t *data, size_t length) = 0;
inline void write_command(int cmd) { write_command(cmd, nullptr, 0); }
inline void write_command(int cmd, const uint8_t data) { write_command(cmd, &data, 1); }
virtual void write_command(int cmd, const uint8_t *data, size_t length) = 0;

virtual void dump_config(){};

virtual void begin_transaction() = 0;
virtual void end_transaction() = 0;
void begin_transaction() {
if (++this->transaction_counter == 0) {
do_begin_transaction();
}
}
void end_transaction() {
if (this->transaction_counter <= 1) {
do_end_transaction();
this->transaction_counter = 0;
} else {
this->transaction_counter--;
}
}

void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }

Expand All @@ -65,7 +78,11 @@ class ByteBus {
void set_dc_data(bool data) { this->dc_pin_->digital_write(data); }

protected:
virtual void do_begin_transaction() = 0;
virtual void do_end_transaction() = 0;

GPIOPin *dc_pin_{byte_bus::NULL_PIN};
int transaction_counter{0};
};
} // namespace byte_bus
} // namespace esphome
76 changes: 56 additions & 20 deletions esphome/components/i80/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv

from esphome.components import byte_bus
from esphome import pins

Expand All @@ -8,7 +9,6 @@
CONF_ID,
CONF_DATA_RATE,
CONF_CS_PIN,
CONF_CLIENT_ID,
CONF_DC_PIN,
)

Expand All @@ -17,31 +17,49 @@

i80_ns = cg.esphome_ns.namespace("i80")
I80Component = i80_ns.class_("I80Component", cg.Component)
I80Client = i80_ns.class_("I80Client", byte_bus.ByteBus)
I80ByteBus = i80_ns.class_("I80ByteBus", byte_bus.ByteBus)

CONF_RD_PIN = "rd_pin"
CONF_WR_PIN = "wr_pin"
CONF_I80_ID = "i80_id"


def validate_data_pins():
pass


CONFIG_SCHEMA = cv.All(
cv.ensure_list(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(I80Component),
cv.Required(CONF_DATA_PINS): cv.All(
cv.ensure_list(pins.internal_gpio_output_pin_number),
cv.Length(min=8, max=8),
cv.Length(min=8, max=16),
),
cv.Required(CONF_WR_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_RD_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_DC_PIN): pins.internal_gpio_output_pin_schema,
}
},
validate_data_pins,
)
),
cv.only_with_esp_idf,
)


async def to_code(configs):
cg.add_define("USE_I80")
for conf in configs:
wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN])
dc = await cg.gpio_pin_expression(conf[CONF_DC_PIN])

var = cg.new_Pvariable(conf[CONF_ID], wr, dc, conf[CONF_DATA_PINS])
await cg.register_component(var, conf)
if rd := conf.get(CONF_RD_PIN):
rd = await cg.gpio_pin_expression(rd)
cg.add(var.set_rd_pin(rd))


def i80_client_schema(
cs_pin_required=False,
default_data_rate="2MHz",
Expand All @@ -54,7 +72,6 @@ def i80_client_schema(
schema = {
cv.GenerateID(CONF_I80_ID): cv.use_id(I80Component),
cv.Optional(CONF_DATA_RATE, default=default_data_rate): cv.frequency,
cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(I80Client),
}
if cs_pin_required:
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
Expand All @@ -63,23 +80,42 @@ def i80_client_schema(
return cv.Schema(schema)


async def to_code(configs):
cg.add_define("USE_I80")
for conf in configs:
wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN])
dc = await cg.gpio_pin_expression(conf[CONF_DC_PIN])
var = cg.new_Pvariable(conf[CONF_ID], wr, dc, conf[CONF_DATA_PINS])
await cg.register_component(var, conf)
if rd := conf.get(CONF_RD_PIN):
rd = await cg.gpio_pin_expression(rd)
cg.add(var.set_rd_pin(rd))
def final_validate_databus_schema(name: str, pin_count, config=None):
data_pins = byte_bus.get_config_from_id(config[CONF_I80_ID])[CONF_DATA_PINS]
if len(data_pins) != pin_count:
i80_id = config[CONF_I80_ID].id
bus_type = config["bus_type"]
raise cv.Invalid(
f"The {bus_type} bus type requires that the {i80_id} component has {pin_count} pins declared."
)
return config


async def create_i80_client(config):
id = config[CONF_CLIENT_ID]
var = cg.new_Pvariable(id)
@byte_bus.include_databus(
"i80",
bus_class=I80ByteBus,
schema=i80_client_schema(),
final_validate=final_validate_databus_schema,
final_args={"pin_count": 8},
)
@byte_bus.include_databus(
"par8",
bus_class=I80ByteBus,
schema=i80_client_schema(),
final_validate=final_validate_databus_schema,
final_args={"pin_count": 8},
)
@byte_bus.include_databus(
"par16",
bus_class=I80ByteBus,
schema=i80_client_schema(),
final_validate=final_validate_databus_schema,
final_args={"pin_count": 16},
)
async def create_i80_databus(config, var, bustype):
cg.add(var.set_parent(await cg.get_variable(config[CONF_I80_ID])))
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
if pin := config.get(CONF_CS_PIN):
cg.add(var.set_cs_pin(await cg.gpio_pin_expression(pin)))
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))

return var
35 changes: 35 additions & 0 deletions esphome/components/i80/i80_component.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "i80_component.h"
#include "i80_esp_idf.cpp"
#ifdef USE_I80

namespace esphome {
Expand All @@ -12,6 +13,40 @@ void I80Component::dump_config() {
}
}

void I80Component::setup() {
auto *bus = new I80BusIdf(this->wr_pin_, this->dc_pin_, this->data_pins_); // NOLINT
// write pin is unused, but pulled high (inactive) if present
if (this->rd_pin_ != nullptr) {
this->rd_pin_->setup();
this->rd_pin_->digital_write(true);
}
if (bus->is_failed())
this->mark_failed();
this->bus_ = bus;
}
I80Delegate *I80Component::register_device(I80ByteBus *device, GPIOPin *cs_pin, unsigned int data_rate) {
if (this->devices_.count(device) != 0) {
ESP_LOGE(TAG, "i80 device already registered");
return this->devices_[device];
}
auto *delegate = this->bus_->get_delegate(cs_pin, data_rate); // NOLINT
this->devices_[device] = delegate;
return delegate;
}
void I80Component::unregister_device(I80ByteBus *device) {
if (this->devices_.count(device) == 0) {
esph_log_e(TAG, "i80 device not registered");
return;
}
delete this->devices_[device]; // NOLINT
this->devices_.erase(device);
}

void I80ByteBus::dump_config() {
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
LOG_PIN(" CS Pin: ", this->cs_);
}

} // namespace i80
} // namespace esphome

Expand Down
Loading