Added tlc59208f_ext

This commit is contained in:
Chris Stuurman 2026-01-17 23:18:27 +02:00
parent 27a62002fa
commit 783879ae2a
5 changed files with 575 additions and 41 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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<uint8_t>(duty_rounded);
this->parent_->set_channel_value_(this->channel_, duty);
}
} // namespace tlc59208f_ext
} // namespace esphome

View File

@ -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 <esphome/core/gpio.h>
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<TLC59208FOutput> {
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

View File

@ -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<int>(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<int>(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);