diff --git a/CODEOWNERS b/CODEOWNERS index 59648b6dbc..18694b50ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie +esphome/components/cd74hc4067/* @asoehlke esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py new file mode 100644 index 0000000000..f8efdf4b2a --- /dev/null +++ b/esphome/components/cd74hc4067/__init__.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +from esphome import pins +import esphome.config_validation as cv +from esphome.const import ( + CONF_DELAY, + CONF_ID, +) + +CODEOWNERS = ["@asoehlke"] +AUTO_LOAD = ["sensor", "voltage_sampler"] + +cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") + +CD74HC4067Component = cd74hc4067_ns.class_( + "CD74HC4067Component", cg.Component, cg.PollingComponent +) + +CONF_PIN_S0 = "pin_s0" +CONF_PIN_S1 = "pin_s1" +CONF_PIN_S2 = "pin_s2" +CONF_PIN_S3 = "pin_s3" + +DEFAULT_DELAY = "2ms" + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CD74HC4067Component), + cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DELAY, default=DEFAULT_DELAY + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0]) + cg.add(var.set_pin_s0(pin_s0)) + pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1]) + cg.add(var.set_pin_s1(pin_s1)) + pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2]) + cg.add(var.set_pin_s2(pin_s2)) + pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3]) + cg.add(var.set_pin_s3(pin_s3)) + + cg.add(var.set_switch_delay(config[CONF_DELAY])) diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp new file mode 100644 index 0000000000..ea789c2d8c --- /dev/null +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -0,0 +1,86 @@ +#include "cd74hc4067.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace cd74hc4067 { + +static const char *const TAG = "cd74hc4067"; + +float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; } + +void CD74HC4067Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CD74HC4067..."); + + this->pin_s0_->setup(); + this->pin_s1_->setup(); + this->pin_s2_->setup(); + this->pin_s3_->setup(); + + // set other pin, so that activate_pin will really switch + this->active_pin_ = 1; + this->activate_pin(0); +} + +void CD74HC4067Component::dump_config() { + ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); + LOG_PIN(" S0 Pin: ", this->pin_s0_); + LOG_PIN(" S1 Pin: ", this->pin_s1_); + LOG_PIN(" S2 Pin: ", this->pin_s2_); + LOG_PIN(" S3 Pin: ", this->pin_s3_); + ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_); +} + +void CD74HC4067Component::activate_pin(uint8_t pin) { + if (this->active_pin_ != pin) { + ESP_LOGD(TAG, "switch to input %d", pin); + + static int mux_channel[16][4] = { + {0, 0, 0, 0}, // channel 0 + {1, 0, 0, 0}, // channel 1 + {0, 1, 0, 0}, // channel 2 + {1, 1, 0, 0}, // channel 3 + {0, 0, 1, 0}, // channel 4 + {1, 0, 1, 0}, // channel 5 + {0, 1, 1, 0}, // channel 6 + {1, 1, 1, 0}, // channel 7 + {0, 0, 0, 1}, // channel 8 + {1, 0, 0, 1}, // channel 9 + {0, 1, 0, 1}, // channel 10 + {1, 1, 0, 1}, // channel 11 + {0, 0, 1, 1}, // channel 12 + {1, 0, 1, 1}, // channel 13 + {0, 1, 1, 1}, // channel 14 + {1, 1, 1, 1} // channel 15 + }; + this->pin_s0_->digital_write(mux_channel[pin][0]); + this->pin_s1_->digital_write(mux_channel[pin][1]); + this->pin_s2_->digital_write(mux_channel[pin][2]); + this->pin_s3_->digital_write(mux_channel[pin][3]); + // small delay is needed to let the multiplexer switch + delay(this->switch_delay_); + this->active_pin_ = pin; + } +} + +CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {} + +void CD74HC4067Sensor::update() { + float value_v = this->sample(); + this->publish_state(value_v); +} + +float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; } + +float CD74HC4067Sensor::sample() { + this->parent_->activate_pin(this->pin_); + return this->source_->sample(); +} + +void CD74HC4067Sensor::dump_config() { + LOG_SENSOR(TAG, "CD74HC4067 Sensor", this); + ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace cd74hc4067 +} // namespace esphome diff --git a/esphome/components/cd74hc4067/cd74hc4067.h b/esphome/components/cd74hc4067/cd74hc4067.h new file mode 100644 index 0000000000..4a5c2e4e35 --- /dev/null +++ b/esphome/components/cd74hc4067/cd74hc4067.h @@ -0,0 +1,65 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" + +namespace esphome { +namespace cd74hc4067 { + +class CD74HC4067Component : public Component { + public: + /// Set up the internal sensor array. + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + /// setting pin active by setting the right combination of the four multiplexer input pins + void activate_pin(uint8_t pin); + + /// set the pin connected to multiplexer control pin 0 + void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; } + /// set the pin connected to multiplexer control pin 1 + void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; } + /// set the pin connected to multiplexer control pin 2 + void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; } + /// set the pin connected to multiplexer control pin 3 + void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; } + + /// set the delay needed after an input switch + void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; } + + private: + InternalGPIOPin *pin_s0_; + InternalGPIOPin *pin_s1_; + InternalGPIOPin *pin_s2_; + InternalGPIOPin *pin_s3_; + /// the currently active pin + uint8_t active_pin_; + uint32_t switch_delay_; +}; + +class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { + public: + CD74HC4067Sensor(CD74HC4067Component *parent); + + void update() override; + + void dump_config() override; + /// `HARDWARE_LATE` setup priority. + float get_setup_priority() const override; + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; } + + float sample() override; + + protected: + CD74HC4067Component *parent_; + /// The sampling source to read values from. + voltage_sampler::VoltageSampler *source_; + + uint8_t pin_; +}; +} // namespace cd74hc4067 +} // namespace esphome diff --git a/esphome/components/cd74hc4067/sensor.py b/esphome/components/cd74hc4067/sensor.py new file mode 100644 index 0000000000..7c7cf9ccb7 --- /dev/null +++ b/esphome/components/cd74hc4067/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import ( + CONF_ID, + CONF_SENSOR, + CONF_NUMBER, + ICON_FLASH, + UNIT_VOLT, + STATE_CLASS_MEASUREMENT, + DEVICE_CLASS_VOLTAGE, +) +from . import cd74hc4067_ns, CD74HC4067Component + +DEPENDENCIES = ["cd74hc4067"] + +CD74HC4067Sensor = cd74hc4067_ns.class_( + "CD74HC4067Sensor", + sensor.Sensor, + cg.PollingComponent, + voltage_sampler.VoltageSampler, +) + +CONF_CD74HC4067_ID = "cd74hc4067_id" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_FLASH, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(CD74HC4067Sensor), + cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component), + cv.Required(CONF_NUMBER): cv.int_range(0, 15), + cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), + } + ) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_CD74HC4067_ID]) + + var = cg.new_Pvariable(config[CONF_ID], parent) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + cg.add(var.set_pin(config[CONF_NUMBER])) + + sens = await cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_source(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 82e3a58725..607d985704 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -378,6 +378,10 @@ sensor: - 400 -> 500 - -50 -> -1000 - -100 -> -10000 + - platform: cd74hc4067 + id: cd74hc4067_0 + number: 0 + sensor: my_sensor - platform: resistance sensor: my_sensor configuration: DOWNSTREAM @@ -1345,3 +1349,9 @@ dsmr: daly_bms: update_interval: 20s uart_id: uart1 + +cd74hc4067: + pin_s0: GPIO12 + pin_s1: GPIO13 + pin_s2: GPIO14 + pin_s3: GPIO15