diff --git a/CODEOWNERS b/CODEOWNERS index 1da55891fd..d30060fe3b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -208,6 +208,7 @@ esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/sml/* @alengwenus esphome/components/smt100/* @piechade +esphome/components/sn74hc165/* @jesserockz esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/spi/* @esphome/core diff --git a/esphome/components/sn74hc165/__init__.py b/esphome/components/sn74hc165/__init__.py new file mode 100644 index 0000000000..85d0220a88 --- /dev/null +++ b/esphome/components/sn74hc165/__init__.py @@ -0,0 +1,87 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import ( + CONF_ID, + CONF_MODE, + CONF_NUMBER, + CONF_INVERTED, + CONF_DATA_PIN, + CONF_CLOCK_PIN, + CONF_INPUT, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = [] +MULTI_CONF = True + +sn74hc165_ns = cg.esphome_ns.namespace("sn74hc165") + +SN74HC165Component = sn74hc165_ns.class_("SN74HC165Component", cg.Component) +SN74HC165GPIOPin = sn74hc165_ns.class_( + "SN74HC165GPIOPin", cg.GPIOPin, cg.Parented.template(SN74HC165Component) +) + +CONF_SN74HC165 = "sn74hc165" +CONF_LOAD_PIN = "load_pin" +CONF_CLOCK_INHIBIT_PIN = "clock_inhibit_pin" +CONF_SR_COUNT = "sr_count" +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(SN74HC165Component), + cv.Required(CONF_DATA_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_LOAD_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_CLOCK_INHIBIT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(min=1, max=256), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + data_pin = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data_pin)) + clock_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock_pin)) + load_pin = await cg.gpio_pin_expression(config[CONF_LOAD_PIN]) + cg.add(var.set_load_pin(load_pin)) + if CONF_CLOCK_INHIBIT_PIN in config: + clock_inhibit_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_INHIBIT_PIN]) + cg.add(var.set_clock_inhibit_pin(clock_inhibit_pin)) + + cg.add(var.set_sr_count(config[CONF_SR_COUNT])) + + +def _validate_input_mode(value): + if value is not True: + raise cv.Invalid("Only input mode is supported") + return value + + +SN74HC165_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(SN74HC165GPIOPin), + cv.Required(CONF_SN74HC165): cv.use_id(SN74HC165Component), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=2048, max_included=False), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=True): cv.All( + cv.boolean, _validate_input_mode + ), + }, + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC165, SN74HC165_PIN_SCHEMA) +async def sn74hc165_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_parented(var, config[CONF_SN74HC165]) + + cg.add(var.set_pin(config[CONF_NUMBER])) + cg.add(var.set_inverted(config[CONF_INVERTED])) + return var diff --git a/esphome/components/sn74hc165/sn74hc165.cpp b/esphome/components/sn74hc165/sn74hc165.cpp new file mode 100644 index 0000000000..6c89544db4 --- /dev/null +++ b/esphome/components/sn74hc165/sn74hc165.cpp @@ -0,0 +1,67 @@ +#include "sn74hc165.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sn74hc165 { + +static const char *const TAG = "sn74hc165"; + +void SN74HC165Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up SN74HC165..."); + + // initialize pins + this->clock_pin_->setup(); + this->data_pin_->setup(); + this->load_pin_->setup(); + this->clock_pin_->digital_write(false); + this->load_pin_->digital_write(false); + + if (this->clock_inhibit_pin_ != nullptr) { + this->clock_inhibit_pin_->setup(); + this->clock_inhibit_pin_->digital_write(true); + } + + // read state from shift register + this->read_gpio_(); +} + +void SN74HC165Component::loop() { this->read_gpio_(); } + +void SN74HC165Component::dump_config() { ESP_LOGCONFIG(TAG, "SN74HC165:"); } + +bool SN74HC165Component::digital_read_(uint16_t pin) { + if (pin >= this->sr_count_ * 8) { + ESP_LOGE(TAG, "Pin %u is out of range! Maximum pin number with %u chips in series is %u", pin, this->sr_count_, + (this->sr_count_ * 8) - 1); + return false; + } + return this->input_bits_[pin]; +} + +void SN74HC165Component::read_gpio_() { + this->load_pin_->digital_write(false); + delayMicroseconds(5); + this->load_pin_->digital_write(true); + delayMicroseconds(5); + + if (this->clock_inhibit_pin_ != nullptr) + this->clock_inhibit_pin_->digital_write(false); + + for (int16_t i = (this->sr_count_ * 8) - 1; i >= 0; i--) { + this->input_bits_[i] = this->data_pin_->digital_read(); + this->clock_pin_->digital_write(true); + this->clock_pin_->digital_write(false); + } + + if (this->clock_inhibit_pin_ != nullptr) + this->clock_inhibit_pin_->digital_write(true); +} + +float SN74HC165Component::get_setup_priority() const { return setup_priority::IO; } + +bool SN74HC165GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_); } + +std::string SN74HC165GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC165", 18, pin_); } + +} // namespace sn74hc165 +} // namespace esphome diff --git a/esphome/components/sn74hc165/sn74hc165.h b/esphome/components/sn74hc165/sn74hc165.h new file mode 100644 index 0000000000..c349d079ae --- /dev/null +++ b/esphome/components/sn74hc165/sn74hc165.h @@ -0,0 +1,61 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace sn74hc165 { + +class SN74HC165Component : public Component { + public: + SN74HC165Component() = default; + + void setup() override; + void loop() override; + float get_setup_priority() const override; + void dump_config() override; + + void set_data_pin(GPIOPin *pin) { this->data_pin_ = pin; } + void set_clock_pin(GPIOPin *pin) { this->clock_pin_ = pin; } + void set_load_pin(GPIOPin *pin) { this->load_pin_ = pin; } + void set_clock_inhibit_pin(GPIOPin *pin) { this->clock_inhibit_pin_ = pin; } + void set_sr_count(uint8_t count) { + this->sr_count_ = count; + this->input_bits_.resize(count * 8); + } + + protected: + friend class SN74HC165GPIOPin; + bool digital_read_(uint16_t pin); + void read_gpio_(); + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; + GPIOPin *load_pin_; + GPIOPin *clock_inhibit_pin_; + uint8_t sr_count_; + std::vector input_bits_; +}; + +/// Helper class to expose a SC74HC165 pin as an internal input GPIO pin. +class SN74HC165GPIOPin : public GPIOPin, public Parented { + public: + void setup() override {} + void pin_mode(gpio::Flags flags) override {} + bool digital_read() override; + void digital_write(bool value) override{}; + std::string dump_summary() const override; + + void set_pin(uint16_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + + protected: + uint16_t pin_; + bool inverted_; +}; + +} // namespace sn74hc165 +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index 30416fad01..596ba5de4e 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -162,6 +162,11 @@ binary_sensor: then: - output.turn_off: Led7 + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 @@ -542,3 +547,11 @@ text_sensor: - ezo_pmp.arbitrary_command: id: hcl_pump command: D,? + +sn74hc165: + id: sn74hc165_hub + data_pin: GPIO12 + clock_pin: GPIO14 + load_pin: GPIO27 + clock_inhibit_pin: GPIO26 + sr_count: 4