diff --git a/components/tlc59208f_ext/__init__.py b/components/tlc59208f_ext/__init__.py new file mode 100644 index 0000000..f7fda8b --- /dev/null +++ b/components/tlc59208f_ext/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import i2c +from esphome import pins +import esphome.config_validation as cv +from esphome.const import CONF_ID + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +tlc59208f_ext_ns = cg.esphome_ns.namespace("tlc59208f_ext") +TLC59208FOutput = tlc59208f_ext_ns.class_("TLC59208FOutput", cg.Component, i2c.I2CDevice) + +CONF_RESET_PIN = "reset_pin" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TLC59208FOutput), + cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + reset_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset_pin)) diff --git a/components/tlc59208f_ext/output.py b/components/tlc59208f_ext/output.py new file mode 100644 index 0000000..c45e3d6 --- /dev/null +++ b/components/tlc59208f_ext/output.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +from esphome.components import output +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_ID + +from . import TLC59208FOutput, tlc59208f_ext_ns + +DEPENDENCIES = ["tlc59208f_ext"] + +TLC59208FChannel = tlc59208f_ext_ns.class_("TLC59208FChannel", output.FloatOutput) +CONF_TLC59208F_ID = "tlc59208f_id" + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(TLC59208FChannel), + cv.GenerateID(CONF_TLC59208F_ID): cv.use_id(TLC59208FOutput), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_TLC59208F_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) diff --git a/components/tlc59208f_ext/tlc59208f_output.cpp b/components/tlc59208f_ext/tlc59208f_output.cpp new file mode 100644 index 0000000..6ad6493 --- /dev/null +++ b/components/tlc59208f_ext/tlc59208f_output.cpp @@ -0,0 +1,209 @@ +#include "tlc59208f_output.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tlc59208f_ext { + +static const char *const TAG = "tlc59208f_ext"; + +// * marks register defaults +// 0*: Register auto increment disabled, 1: Register auto increment enabled +const uint8_t TLC59208F_MODE1_AI2 = (1 << 7); +// 0*: don't auto increment bit 1, 1: auto increment bit 1 +const uint8_t TLC59208F_MODE1_AI1 = (1 << 6); +// 0*: don't auto increment bit 0, 1: auto increment bit 0 +const uint8_t TLC59208F_MODE1_AI0 = (1 << 5); +// 0: normal mode, 1*: low power mode, osc off +const uint8_t TLC59208F_MODE1_SLEEP = (1 << 4); +// 0*: device doesn't respond to i2c bus sub-address 1, 1: responds +const uint8_t TLC59208F_MODE1_SUB1 = (1 << 3); +// 0*: device doesn't respond to i2c bus sub-address 2, 1: responds +const uint8_t TLC59208F_MODE1_SUB2 = (1 << 2); +// 0*: device doesn't respond to i2c bus sub-address 3, 1: responds +const uint8_t TLC59208F_MODE1_SUB3 = (1 << 1); +// 0: device doesn't respond to i2c all-call 3, 1*: responds to all-call +const uint8_t TLC59208F_MODE1_ALLCALL = (1 << 0); + +// 0*: Group dimming, 1: Group blinking +const uint8_t TLC59208F_MODE2_DMBLNK = (1 << 5); +// 0*: Output change on Stop command, 1: Output change on ACK +const uint8_t TLC59208F_MODE2_OCH = (1 << 3); +// 0*: WDT disabled, 1: WDT enabled +const uint8_t TLC59208F_MODE2_WDTEN = (1 << 2); +// WDT timeouts +const uint8_t TLC59208F_MODE2_WDT_5MS = (0 << 0); +const uint8_t TLC59208F_MODE2_WDT_15MS = (1 << 0); +const uint8_t TLC59208F_MODE2_WDT_25MS = (2 << 0); +const uint8_t TLC59208F_MODE2_WDT_35MS = (3 << 0); + +// --- Special function --- +// Call address to perform software reset, no devices will ACK +const uint8_t TLC59208F_SWRST_ADDR = 0x96; //(0x4b 7-bit addr + ~W) +const uint8_t TLC59208F_SWRST_SEQ[2] = {0xa5, 0x5a}; + +// --- Registers ---2 +// Mode register 1 +const uint8_t TLC59208F_REG_MODE1 = 0x00; +// Mode register 2 +const uint8_t TLC59208F_REG_MODE2 = 0x01; +// PWM0 +const uint8_t TLC59208F_REG_PWM0 = 0x02; +// Group PWM +const uint8_t TLC59208F_REG_GROUPPWM = 0x0a; +// Group Freq +const uint8_t TLC59208F_REG_GROUPFREQ = 0x0b; +// LEDOUTx registers +const uint8_t TLC59208F_REG_LEDOUT0 = 0x0c; +const uint8_t TLC59208F_REG_LEDOUT1 = 0x0d; +// Sub-address registers +const uint8_t TLC59208F_REG_SUBADR1 = 0x0e; // default: 0x92 (8-bit addr) +const uint8_t TLC59208F_REG_SUBADR2 = 0x0f; // default: 0x94 (8-bit addr) +const uint8_t TLC59208F_REG_SUBADR3 = 0x10; // default: 0x98 (8-bit addr) +// All call address register +const uint8_t TLC59208F_REG_ALLCALLADR = 0x11; // default: 0xd0 (8-bit addr) + +// --- Output modes --- +static const uint8_t LDR_OFF = 0x00; +static const uint8_t LDR_ON = 0x01; +static const uint8_t LDR_PWM = 0x02; +static const uint8_t LDR_GRPPWM = 0x03; + +void TLC59208FOutput::setup() { + ESP_LOGD(TAG, " Resetting all devices on the bus"); + this->hard_reset(); + // Software reset all devices on the bus + auto errcode = this->bus_->write_readv(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, sizeof TLC59208F_SWRST_SEQ, nullptr, 0); + if (errcode != i2c::ERROR_OK) { + ESP_LOGE(TAG, "RESET failed"); + this->mark_failed("RESET failed"); + return; + } + // Auto increment registers, and respond to all-call address + if (!this->write_byte(TLC59208F_REG_MODE1, TLC59208F_MODE1_AI2 | TLC59208F_MODE1_ALLCALL)) { + ESP_LOGE(TAG, "MODE1 failed"); + this->mark_failed("MODE1 failed"); + return; + } + if (!this->write_byte(TLC59208F_REG_MODE2, this->mode_)) { + ESP_LOGE(TAG, "MODE2 failed"); + this->mark_failed("MODE2 failed"); + return; + } + // Set all 3 outputs to be individually controlled + // TODO: think of a way to support group dimming + if (!this->write_byte(TLC59208F_REG_LEDOUT0, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) { + ESP_LOGE(TAG, "LEDOUT0 failed"); + this->mark_failed("LEDOUT0 failed"); + return; + } + if (!this->write_byte(TLC59208F_REG_LEDOUT1, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) { + ESP_LOGE(TAG, "LEDOUT1 failed"); + this->mark_failed("LEDOUT1 failed"); + return; + } + delayMicroseconds(500); + this->set_all_channels(255); + this->loop(); +} + +void TLC59208FOutput::hard_reset() +{ + if (this->reset_pin_ != nullptr) { + ESP_LOGV(TAG, " Performing hard reset using reset pin"); + this->reset_pin_->digital_write(false); + delayMicroseconds(10); + this->reset_pin_->digital_write(true); + delayMicroseconds(500); + } +} + +void TLC59208FOutput::dump_config() { + ESP_LOGCONFIG(TAG, + "TLC59208F_EXT:\n" + " Mode: 0x%02X", + this->mode_); + LOG_PIN(" RESET Pin:", this->reset_pin_); + if (this->is_failed()) { + ESP_LOGE(TAG, "Setting up TLC59208F failed!"); + } +} + +void TLC59208FOutput::test() +{ + ESP_LOGD(TAG, "TLC59208F_EXT: Running light test"); + this->set_all_channels(255); + this->update_ = true; + this->loop(); + delay(200); + this->set_all_channels(0); + this->update_ = true; + this->loop(); +} + +void TLC59208FOutput::serial_test() +{ + ESP_LOGD(TAG, "TLC59208F_EXT: Running serial test"); + for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) { + this->set_channel_value_(channel, 255); + this->update_ = true; + this->loop(); + delay(20); + this->set_channel_value_(channel, 0); + } + for(uint8_t channel = this->max_channel_; channel >= this->min_channel_; channel--) { + this->set_channel_value_(channel, 255); + this->update_ = true; + this->loop(); + delay(20); + this->set_channel_value_(channel, 0); + } + this->update_ = true; + this->loop(); +} + +void TLC59208FOutput::set_all_channels(uint8_t value) +{ + for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) { + this->set_channel_value_(channel, value); + } + this->update_ = true; +} + +void TLC59208FOutput::loop() +{ + if (this->min_channel_ == 0xFF || !this->update_) + return; + + for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) { + uint8_t pwm = this->pwm_amounts_[channel]; + ESP_LOGVV(TAG, "Channel %02u: pwm=%04u ", channel, pwm); + + uint8_t reg = TLC59208F_REG_PWM0 + channel; + if (!this->write_byte(reg, pwm)) { + this->status_set_warning(); + return; + } + } + + this->status_clear_warning(); + this->update_ = false; +} + +void TLC59208FOutput::register_channel(TLC59208FChannel *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); +} + +void TLC59208FChannel::write_state(float state) { + const uint8_t max_duty = 255; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + this->parent_->set_channel_value_(this->channel_, duty); +} + +} // namespace tlc59208f_ext +} // namespace esphome diff --git a/components/tlc59208f_ext/tlc59208f_output.h b/components/tlc59208f_ext/tlc59208f_output.h new file mode 100644 index 0000000..a275219 --- /dev/null +++ b/components/tlc59208f_ext/tlc59208f_output.h @@ -0,0 +1,79 @@ +#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" +#include + +namespace esphome { +namespace tlc59208f_ext { + +// 0*: Group dimming, 1: Group blinking +extern const uint8_t TLC59208F_MODE2_DMBLNK; +// 0*: Output change on Stop command, 1: Output change on ACK +extern const uint8_t TLC59208F_MODE2_OCH; +// 0*: WDT disabled, 1: WDT enabled +extern const uint8_t TLC59208F_MODE2_WDTEN; +// WDT timeouts +extern const uint8_t TLC59208F_MODE2_WDT_5MS; +extern const uint8_t TLC59208F_MODE2_WDT_15MS; +extern const uint8_t TLC59208F_MODE2_WDT_25MS; +extern const uint8_t TLC59208F_MODE2_WDT_35MS; + +class TLC59208FOutput; + +class TLC59208FChannel : public output::FloatOutput, public Parented { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + + protected: + friend class TLC59208FOutput; + + void write_state(float state) override; + + uint8_t channel_; +}; + +/// TLC59208F float output component. +class TLC59208FOutput : public Component, public i2c::I2CDevice { + private: + void hard_reset(); + + public: + TLC59208FOutput(uint8_t mode = TLC59208F_MODE2_OCH) : mode_(mode) {} + + void register_channel(TLC59208FChannel *channel); + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; } + void loop() override; + void test(); + void serial_test(); + void set_all_channels(uint8_t value); + + protected: + friend TLC59208FChannel; + InternalGPIOPin *reset_pin_; + + void set_channel_value_(uint8_t channel, uint8_t value) { + if (this->pwm_amounts_[channel] != value) + this->update_ = true; + this->pwm_amounts_[channel] = value; + } + + + uint8_t mode_; + + uint8_t min_channel_{0xFF}; + uint8_t max_channel_{0x00}; + uint8_t pwm_amounts_[256] = { + 0, + }; + bool update_{true}; +}; + +} // namespace tlc59208f_ext +} // namespace esphome diff --git a/sthome-ut8.yaml b/sthome-ut8.yaml index 003620e..d0caf83 100644 --- a/sthome-ut8.yaml +++ b/sthome-ut8.yaml @@ -3,7 +3,7 @@ external_components: - source: type: local path: components # Path relative to this YAML file - components: [ ads1115_int, ads1115_pol ] #, ads131m08 ] + components: [ ads1115_int, ads1115_pol, tlc59208f_ext ] #, ads131m08 ] packages: - !include common/wifi.yaml @@ -22,7 +22,9 @@ esphome: then: - lambda: |- ESP_LOGI("esphome", "Device booted"); - + delay(300); + //id(tlc59208f_1)->test(); + //id(tlc59208f_1)->serial_test(); # esp32: board: esp32dev @@ -36,17 +38,17 @@ debug: # Enable logging logger: - level: DEBUG + level: VERY_VERBOSE initial_level: INFO logs: canbus: INFO i2c: DEBUG - i2c.idf: DEBUG + i2c.idf: DEBUG uart: INFO light: INFO sensor: INFO ds1307: INFO - tlc59208f: DEBUG + tlc59208f_ext: VERBOSE text_sensor: INFO ads1115.sensor: INFO ads1115_pol: DEBUG @@ -100,41 +102,21 @@ time: # - logger.log: "Synchronized system clock" # -tlc59208f: - address: 0x50 +tlc59208f_ext: + address: 0x20 id: tlc59208f_1 i2c_id: bus_b + reset_pin: + number: GPIO25 + mode: + output: true + pullup: true -output: - - platform: ledc - pin: - number: GPIO12 #GPIO26 # LED_LOW_BAT - inverted: false #true - id: led_inverter_battery_low - - - platform: tlc59208f - channel: 0 - tlc59208f_id: 'tlc59208f_1' - id: led0 - - - platform: tlc59208f - channel: 1 - tlc59208f_id: 'tlc59208f_1' - id: led1 - -light: - - platform: monochromatic - output: led0 - name: "LED Geyser Temperature 0" - id: led_geyser_temp0 - default_transition_length: 20ms - - - platform: monochromatic - output: led1 - name: "LED Geyser Temperature 1" - id: led_geyser_temp1 - default_transition_length: 20ms - +#interval: +# - interval: 5s +# then: +# - lambda: |- +# id(tlc59208f_1)->test(); binary_sensor: - platform: status @@ -142,6 +124,10 @@ binary_sensor: name: "Status" device_class: connectivity +switch: + - platform: restart + name: "${name} Restart" + id: "restart_switch" i2c: - id: bus_a @@ -150,10 +136,12 @@ i2c: scan: true frequency: 400kHz - id: bus_b - sda: GPIO26 - scl: GPIO1 + sda: GPIO16 + scl: GPIO17 scan: true - frequency: 100kHz + frequency: 20kHz + sda_pullup_enabled: true + scl_pullup_enabled: true ads1115_int: - address: 0x4A @@ -234,6 +222,10 @@ sensor: # if(abs(x) < 0.1) # return 0.0; # return x; + on_value: + then: + - lambda: |- + id(set_indicators).execute(0.0, x); on_value_range: - below: 5.0 then: @@ -371,7 +363,6 @@ sensor: device_class: voltage state_class: measurement - # Report wifi signal strength every 5 min if changed - platform: wifi_signal name: WiFi Signal @@ -406,6 +397,101 @@ sensor: (seconds_str + "s") ).c_str(); +output: + - platform: ledc + pin: + number: GPIO12 #GPIO26 # LED_LOW_BAT + inverted: false #true + id: led_inverter_battery_low + + - platform: tlc59208f_ext + channel: 0 + tlc59208f_id: 'tlc59208f_1' + id: led0 + + - platform: tlc59208f_ext + channel: 1 + tlc59208f_id: 'tlc59208f_1' + id: led1 + + - platform: tlc59208f_ext + channel: 2 + tlc59208f_id: 'tlc59208f_1' + id: led2 + + - platform: tlc59208f_ext + channel: 3 + tlc59208f_id: 'tlc59208f_1' + id: led3 + + - platform: tlc59208f_ext + channel: 4 + tlc59208f_id: 'tlc59208f_1' + id: led4 + + - platform: tlc59208f_ext + channel: 5 + tlc59208f_id: 'tlc59208f_1' + id: led5 + - platform: tlc59208f_ext + + channel: 6 + tlc59208f_id: 'tlc59208f_1' + id: led6 + + - platform: tlc59208f_ext + channel: 7 + tlc59208f_id: 'tlc59208f_1' + id: led7 + +light: + - platform: monochromatic + output: led0 + name: "LED Geyser Temperature 0" + id: led_geyser_temp0 + default_transition_length: 20ms + + - platform: monochromatic + output: led1 + name: "LED Geyser Temperature 1" + id: led_geyser_temp1 + default_transition_length: 20ms + + - platform: monochromatic + output: led2 + name: "LED Geyser Temperature 2" + id: led_geyser_temp2 + default_transition_length: 20ms + + - platform: monochromatic + output: led3 + name: "LED Geyser Temperature 3" + id: led_geyser_temp3 + default_transition_length: 20ms + + - platform: monochromatic + output: led4 + name: "LED Geyser Temperature 4" + id: led_geyser_temp4 + default_transition_length: 20ms + + - platform: monochromatic + output: led5 + name: "LED Geyser Temperature 5" + id: led_geyser_temp5 + default_transition_length: 20ms + + - platform: monochromatic + output: led6 + name: "LED Geyser Temperature 6" + id: led_geyser_temp6 + default_transition_length: 20ms + + - platform: monochromatic + output: led7 + name: "LED Geyser Temperature 7" + id: led_geyser_temp7 + default_transition_length: 20ms text_sensor: - platform: debug @@ -425,3 +511,104 @@ text_sensor: mac_address: name: Mac Address +script: + - id: set_indicators + parameters: + low_value: double + high_value: double + then: + - lambda: |- + double led_on = 1.0; + int led_count = 8; + double min_value = 0.7; + double max_value = 0.8; + if(low_value < min_value) { + low_value = min_value; + } + double step_size = (max_value - min_value) / led_count; + double bottom_point = (low_value - min_value) / step_size; + int led_bot = static_cast(std::trunc(bottom_point)); + double led_bot_int = 1.0 + led_bot - bottom_point; + double top_point = (high_value - min_value) / step_size; + int led_top = static_cast(std::trunc(top_point)); + double led_top_int = top_point - led_top; + double led_0 = 0; + double led_1 = 0; + double led_2 = 0; + double led_3 = 0; + double led_4 = 0; + double led_5 = 0; + double led_6 = 0; + double led_7 = 0; + if (led_top == 0) led_0 = led_top_int * led_on; + if (led_top == 1) led_1 = led_top_int * led_on; + if (led_top == 2) led_2 = led_top_int * led_on; + if (led_top == 3) led_3 = led_top_int * led_on; + if (led_top == 4) led_4 = led_top_int * led_on; + if (led_top == 5) led_5 = led_top_int * led_on; + if (led_top == 6) led_6 = led_top_int * led_on; + if (led_top == 7) led_7 = led_top_int * led_on; + if (led_bot == 0) { + led_0 = led_bot_int * led_on; + if (led_top > 1) led_1 = led_on; + if (led_top > 2) led_2 = led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 1) { + led_1 = led_bot_int * led_on; + if (led_top > 2) led_2 = led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 2) { + led_2 = led_bot_int * led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 3) { + led_3 = led_bot_int * led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 4) { + led_4 = led_bot_int * led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 5) { + led_5 = led_bot_int * led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 6) { + led_6 = led_bot_int * led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 7) { + led_7 = led_bot_int * led_on; + if (led_top > 7) led_7 = 1; + } + id(led_geyser_temp0).turn_on().set_brightness(led_0).perform(); + id(led_geyser_temp1).turn_on().set_brightness(led_1).perform(); + id(led_geyser_temp2).turn_on().set_brightness(led_2).perform(); + id(led_geyser_temp3).turn_on().set_brightness(led_3).perform(); + id(led_geyser_temp4).turn_on().set_brightness(led_4).perform(); + id(led_geyser_temp5).turn_on().set_brightness(led_5).perform(); + id(led_geyser_temp6).turn_on().set_brightness(led_6).perform(); + id(led_geyser_temp7).turn_on().set_brightness(led_7).perform(); + //ESP_LOGI("geyser","bot: %f, top: %f 0: %0.2f 1: %0.2f 2: %0.2f 3: %0.2f 4: %0.2f 5: %0.2f 6: %0.2f 7: %0.2f ", low_value, high_value, led_0, led_1, led_2, led_3, led_4, led_5, led_6, led_7); +