diff --git a/CODEOWNERS b/CODEOWNERS index 3cb38fce06..9237ff813c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,7 @@ esphome/components/cs5460a/* @balrog-kun esphome/components/cse7761/* @berfenger esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx +esphome/components/dac7678/* @NickB1 esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter diff --git a/esphome/components/dac7678/__init__.py b/esphome/components/dac7678/__init__.py new file mode 100644 index 0000000000..b6cd2b384e --- /dev/null +++ b/esphome/components/dac7678/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +AUTO_LOAD = ["output"] +CODEOWNERS = ["@NickB1"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +dac7678_ns = cg.esphome_ns.namespace("dac7678") +DAC7678Output = dac7678_ns.class_("DAC7678Output", cg.Component, i2c.I2CDevice) +CONF_INTERNAL_REFERENCE = "internal_reference" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DAC7678Output), + cv.Optional(CONF_INTERNAL_REFERENCE, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x48)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_internal_reference(config[CONF_INTERNAL_REFERENCE])) + await i2c.register_i2c_device(var, config) + return var diff --git a/esphome/components/dac7678/dac7678_output.cpp b/esphome/components/dac7678/dac7678_output.cpp new file mode 100644 index 0000000000..bfb18e4a4e --- /dev/null +++ b/esphome/components/dac7678/dac7678_output.cpp @@ -0,0 +1,83 @@ +#include "dac7678_output.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace dac7678 { + +static const char *const TAG = "dac7678"; + +static const uint8_t DAC7678_REG_INPUT_N = 0x00; +static const uint8_t DAC7678_REG_SELECT_UPDATE_N = 0x10; +static const uint8_t DAC7678_REG_WRITE_N_UPDATE_ALL = 0x20; +static const uint8_t DAC7678_REG_WRITE_N_UPDATE_N = 0x30; +static const uint8_t DAC7678_REG_POWER = 0x40; +static const uint8_t DAC7678_REG_CLEAR_CODE = 0x50; +static const uint8_t DAC7678_REG_LDAC = 0x60; +static const uint8_t DAC7678_REG_SOFTWARE_RESET = 0x70; +static const uint8_t DAC7678_REG_INTERNAL_REF_0 = 0x80; +static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90; + +void DAC7678Output::setup() { + ESP_LOGCONFIG(TAG, "Setting up DAC7678OutputComponent..."); + + ESP_LOGV(TAG, "Resetting device..."); + + // Reset device + if (!this->write_byte_16(DAC7678_REG_SOFTWARE_RESET, 0x0000)) { + ESP_LOGE(TAG, "Reset failed"); + this->mark_failed(); + return; + } else + ESP_LOGV(TAG, "Reset succeeded"); + + delayMicroseconds(1000); + + // Set internal reference + if (this->internal_reference_) { + if (!this->write_byte_16(DAC7678_REG_INTERNAL_REF_0, 1 << 4)) { + ESP_LOGE(TAG, "Set internal reference failed"); + this->mark_failed(); + return; + } else + ESP_LOGV(TAG, "Internal reference enabled"); + } +} + +void DAC7678Output::dump_config() { + if (this->is_failed()) { + ESP_LOGE(TAG, "Setting up DAC7678 failed!"); + } else + ESP_LOGCONFIG(TAG, "DAC7678 initialised"); +} + +void DAC7678Output::register_channel(DAC7678Channel *channel) { + auto c = channel->channel_; + this->min_channel_ = std::min(this->min_channel_, c); + this->max_channel_ = std::max(this->max_channel_, c); + channel->set_parent(this); + ESP_LOGV(TAG, "Registered channel: %01u", channel->channel_); +} + +void DAC7678Output::set_channel_value_(uint8_t channel, uint16_t value) { + if (this->dac_input_reg_[channel] != value) { + ESP_LOGV(TAG, "Channel %01u: input_reg=%04u ", channel, value); + + if (!this->write_byte_16(DAC7678_REG_WRITE_N_UPDATE_N | channel, value << 4)) { + this->status_set_warning(); + return; + } + } + this->dac_input_reg_[channel] = value; + this->status_clear_warning(); +} + +void DAC7678Channel::write_state(float state) { + const float input_rounded = roundf(state * this->full_scale_); + auto input = static_cast(input_rounded); + this->parent_->set_channel_value_(this->channel_, input); +} + +} // namespace dac7678 +} // namespace esphome diff --git a/esphome/components/dac7678/dac7678_output.h b/esphome/components/dac7678/dac7678_output.h new file mode 100644 index 0000000000..abd9875e4c --- /dev/null +++ b/esphome/components/dac7678/dac7678_output.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace dac7678 { + +class DAC7678Output; + +class DAC7678Channel : public output::FloatOutput, public Parented { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + + protected: + friend class DAC7678Output; + + const uint16_t full_scale_ = 0xFFF; + + void write_state(float state) override; + + uint8_t channel_; +}; + +/// DAC7678 float output component. +class DAC7678Output : public Component, public i2c::I2CDevice { + public: + DAC7678Output() {} + + void register_channel(DAC7678Channel *channel); + + void set_internal_reference(const bool value) { this->internal_reference_ = value; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + friend DAC7678Channel; + + bool internal_reference_; + + void set_channel_value_(uint8_t channel, uint16_t value); + + uint8_t min_channel_{0xFF}; + uint8_t max_channel_{0x00}; + uint16_t dac_input_reg_[8] = { + 0, + }; +}; + +} // namespace dac7678 +} // namespace esphome diff --git a/esphome/components/dac7678/output.py b/esphome/components/dac7678/output.py new file mode 100644 index 0000000000..f41e5c2422 --- /dev/null +++ b/esphome/components/dac7678/output.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID +from . import DAC7678Output, dac7678_ns + +DEPENDENCIES = ["dac7678"] + +DAC7678Channel = dac7678_ns.class_("DAC7678Channel", output.FloatOutput) +CONF_DAC7678_ID = "dac7678_id" + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(DAC7678Channel), + cv.GenerateID(CONF_DAC7678_ID): cv.use_id(DAC7678Output), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_DAC7678_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(paren.register_channel(var)) + await output.register_output(var, config) + return var diff --git a/tests/test4.yaml b/tests/test4.yaml index 0dfbeed550..847639289e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -80,6 +80,11 @@ sx1509: mcp3204: cs_pin: GPIO23 +dac7678: + address: 0x4A + id: dac7678_hub1 + internal_reference: true + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -518,6 +523,38 @@ output: pipsolar_id: inverter0 battery_recharge_voltage: id: inverter0_battery_recharge_voltage_out + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 0 + id: 'dac7678_1_ch0' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 1 + id: 'dac7678_1_ch1' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 2 + id: 'dac7678_1_ch2' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 3 + id: 'dac7678_1_ch3' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 4 + id: 'dac7678_1_ch4' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 5 + id: 'dac7678_1_ch5' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 6 + id: 'dac7678_1_ch6' + - platform: dac7678 + dac7678_id: 'dac7678_hub1' + channel: 7 + id: 'dac7678_1_ch7' esp32_camera: name: ESP-32 Camera data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19]