From cbd8d70431decfcaaf1c36e931a693f9ca398d30 Mon Sep 17 00:00:00 2001 From: "Jordan W. Cobb" Date: Mon, 12 Sep 2022 11:30:15 -0400 Subject: [PATCH] Add support for TM1638 Led and Key component (#3340) --- CODEOWNERS | 1 + esphome/components/tm1638/__init__.py | 0 .../tm1638/binary_sensor/__init__.py | 22 ++ .../tm1638/binary_sensor/tm1638_key.cpp | 13 + .../tm1638/binary_sensor/tm1638_key.h | 19 ++ esphome/components/tm1638/display.py | 55 ++++ esphome/components/tm1638/output/__init__.py | 25 ++ .../tm1638/output/tm1638_output_led.cpp | 17 ++ .../tm1638/output/tm1638_output_led.h | 25 ++ esphome/components/tm1638/sevenseg.h | 107 +++++++ esphome/components/tm1638/switch/__init__.py | 24 ++ .../tm1638/switch/tm1638_switch_led.cpp | 20 ++ .../tm1638/switch/tm1638_switch_led.h | 23 ++ esphome/components/tm1638/tm1638.cpp | 288 ++++++++++++++++++ esphome/components/tm1638/tm1638.h | 81 +++++ esphome/const.py | 2 + tests/test5.yaml | 133 ++++++++ 17 files changed, 855 insertions(+) create mode 100644 esphome/components/tm1638/__init__.py create mode 100644 esphome/components/tm1638/binary_sensor/__init__.py create mode 100644 esphome/components/tm1638/binary_sensor/tm1638_key.cpp create mode 100644 esphome/components/tm1638/binary_sensor/tm1638_key.h create mode 100644 esphome/components/tm1638/display.py create mode 100644 esphome/components/tm1638/output/__init__.py create mode 100644 esphome/components/tm1638/output/tm1638_output_led.cpp create mode 100644 esphome/components/tm1638/output/tm1638_output_led.h create mode 100644 esphome/components/tm1638/sevenseg.h create mode 100644 esphome/components/tm1638/switch/__init__.py create mode 100644 esphome/components/tm1638/switch/tm1638_switch_led.cpp create mode 100644 esphome/components/tm1638/switch/tm1638_switch_led.h create mode 100644 esphome/components/tm1638/tm1638.cpp create mode 100644 esphome/components/tm1638/tm1638.h diff --git a/CODEOWNERS b/CODEOWNERS index 85ac107975..3a1087191c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -230,6 +230,7 @@ esphome/components/time/* @OttoWinter esphome/components/tlc5947/* @rnauber esphome/components/tm1621/* @Philippe12 esphome/components/tm1637/* @glmnet +esphome/components/tm1638/* @skykingjwc esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka diff --git a/esphome/components/tm1638/__init__.py b/esphome/components/tm1638/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/tm1638/binary_sensor/__init__.py b/esphome/components/tm1638/binary_sensor/__init__.py new file mode 100644 index 0000000000..7262d9e9e1 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/__init__.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_KEY +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638Key = tm1638_ns.class_("TM1638Key", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Key), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_KEY): cv.int_range(min=0, max=15), + } +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + cg.add(var.set_keycode(config[CONF_KEY])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(hub.register_listener(var)) diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.cpp b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp new file mode 100644 index 0000000000..c143bafaea --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp @@ -0,0 +1,13 @@ +#include "tm1638_key.h" + +namespace esphome { +namespace tm1638 { + +void TM1638Key::keys_update(uint8_t keys) { + bool pressed = keys & (1 << key_code_); + if (pressed != this->state) + this->publish_state(pressed); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.h b/esphome/components/tm1638/binary_sensor/tm1638_key.h new file mode 100644 index 0000000000..0ea385f434 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638Key : public binary_sensor::BinarySensor, public KeyListener { + public: + void set_keycode(uint8_t key_code) { key_code_ = key_code; }; + void keys_update(uint8_t keys) override; + + protected: + uint8_t key_code_{0}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/display.py b/esphome/components/tm1638/display.py new file mode 100644 index 0000000000..6339983674 --- /dev/null +++ b/esphome/components/tm1638/display.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_ID, + CONF_INTENSITY, + CONF_LAMBDA, + CONF_CLK_PIN, + CONF_DIO_PIN, + CONF_STB_PIN, +) + +CODEOWNERS = ["@skykingjwc"] + +CONF_TM1638_ID = "tm1638_id" + +tm1638_ns = cg.esphome_ns.namespace("tm1638") +TM1638Component = tm1638_ns.class_("TM1638Component", cg.PollingComponent) +TM1638ComponentRef = TM1638Component.operator("ref") + + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Component), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_STB_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_INTENSITY, default=7): cv.int_range(min=0, max=8), + } +).extend(cv.polling_component_schema("1s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await display.register_display(var, config) + + clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) + cg.add(var.set_clk_pin(clk)) + + dio = await cg.gpio_pin_expression(config[CONF_DIO_PIN]) + cg.add(var.set_dio_pin(dio)) + + stb = await cg.gpio_pin_expression(config[CONF_STB_PIN]) + cg.add(var.set_stb_pin(stb)) + + cg.add(var.set_intensity(config[CONF_INTENSITY])) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(TM1638ComponentRef, "it")], return_type=cg.void + ) + + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1638/output/__init__.py b/esphome/components/tm1638/output/__init__.py new file mode 100644 index 0000000000..2d982e409d --- /dev/null +++ b/esphome/components/tm1638/output/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_ID, CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638OutputLed = tm1638_ns.class_("TM1638OutputLed", output.BinaryOutput, cg.Component) + + +CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638OutputLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await output.register_output(var, config) + await cg.register_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/output/tm1638_output_led.cpp b/esphome/components/tm1638/output/tm1638_output_led.cpp new file mode 100644 index 0000000000..ea1c84e64b --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.cpp @@ -0,0 +1,17 @@ +#include "tm1638_output_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638OutputLed::write_state(bool state) { tm1638_->set_led(led_, state); } + +void TM1638OutputLed::dump_config() { + LOG_BINARY_OUTPUT(this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/output/tm1638_output_led.h b/esphome/components/tm1638/output/tm1638_output_led.h new file mode 100644 index 0000000000..6aa1015aae --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/binary_output.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638OutputLed : public output::BinaryOutput, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + + TM1638Component *tm1638_; + int led_; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/sevenseg.h b/esphome/components/tm1638/sevenseg.h new file mode 100644 index 0000000000..e20a55a69f --- /dev/null +++ b/esphome/components/tm1638/sevenseg.h @@ -0,0 +1,107 @@ +#pragma once + +namespace esphome { +namespace tm1638 { +namespace TM1638Translation { + +const unsigned char SEVEN_SEG[] PROGMEM = { + 0x00, /* (space) */ + 0x86, /* ! */ + 0x22, /* " */ + 0x7E, /* # */ + 0x6D, /* $ */ + 0xD2, /* % */ + 0x46, /* & */ + 0x20, /* ' */ + 0x29, /* ( */ + 0x0B, /* ) */ + 0x21, /* * */ + 0x70, /* + */ + 0x10, /* , */ + 0x40, /* - */ + 0x80, /* . */ + 0x52, /* / */ + 0x3F, /* 0 */ + 0x06, /* 1 */ + 0x5B, /* 2 */ + 0x4F, /* 3 */ + 0x66, /* 4 */ + 0x6D, /* 5 */ + 0x7D, /* 6 */ + 0x07, /* 7 */ + 0x7F, /* 8 */ + 0x6F, /* 9 */ + 0x09, /* : */ + 0x0D, /* ; */ + 0x61, /* < */ + 0x48, /* = */ + 0x43, /* > */ + 0xD3, /* ? */ + 0x5F, /* @ */ + 0x77, /* A */ + 0x7C, /* B */ + 0x39, /* C */ + 0x5E, /* D */ + 0x79, /* E */ + 0x71, /* F */ + 0x3D, /* G */ + 0x76, /* H */ + 0x30, /* I */ + 0x1E, /* J */ + 0x75, /* K */ + 0x38, /* L */ + 0x15, /* M */ + 0x37, /* N */ + 0x3F, /* O */ + 0x73, /* P */ + 0x6B, /* Q */ + 0x33, /* R */ + 0x6D, /* S */ + 0x78, /* T */ + 0x3E, /* U */ + 0x3E, /* V */ + 0x2A, /* W */ + 0x76, /* X */ + 0x6E, /* Y */ + 0x5B, /* Z */ + 0x39, /* [ */ + 0x64, /* \ */ + 0x0F, /* ] */ + 0x23, /* ^ */ + 0x08, /* _ */ + 0x02, /* ` */ + 0x5F, /* a */ + 0x7C, /* b */ + 0x58, /* c */ + 0x5E, /* d */ + 0x7B, /* e */ + 0x71, /* f */ + 0x6F, /* g */ + 0x74, /* h */ + 0x10, /* i */ + 0x0C, /* j */ + 0x75, /* k */ + 0x30, /* l */ + 0x14, /* m */ + 0x54, /* n */ + 0x5C, /* o */ + 0x73, /* p */ + 0x67, /* q */ + 0x50, /* r */ + 0x6D, /* s */ + 0x78, /* t */ + 0x1C, /* u */ + 0x1C, /* v */ + 0x14, /* w */ + 0x76, /* x */ + 0x6E, /* y */ + 0x5B, /* z */ + 0x46, /* { */ + 0x30, /* | */ + 0x70, /* } */ + 0x01, /* ~ */ +}; + +}; // namespace TM1638Translation +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/__init__.py b/esphome/components/tm1638/switch/__init__.py new file mode 100644 index 0000000000..ed6aa91d03 --- /dev/null +++ b/esphome/components/tm1638/switch/__init__.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638SwitchLed = tm1638_ns.class_("TM1638SwitchLed", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638SwitchLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.cpp b/esphome/components/tm1638/switch/tm1638_switch_led.cpp new file mode 100644 index 0000000000..60c9e8b4a9 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.cpp @@ -0,0 +1,20 @@ +#include "tm1638_switch_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638SwitchLed::write_state(bool state) { + tm1638_->set_led(led_, state); + publish_state(state); +} + +void TM1638SwitchLed::dump_config() { + LOG_SWITCH("", "TM1638 LED", this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.h b/esphome/components/tm1638/switch/tm1638_switch_led.h new file mode 100644 index 0000000000..10516e0079 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638SwitchLed : public switch_::Switch, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + TM1638Component *tm1638_; + int led_; +}; +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp new file mode 100644 index 0000000000..526b53f601 --- /dev/null +++ b/esphome/components/tm1638/tm1638.cpp @@ -0,0 +1,288 @@ +#include "tm1638.h" +#include "sevenseg.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "display.tm1638"; +static const uint8_t TM1638_REGISTER_FIXEDADDRESS = 0x44; +static const uint8_t TM1638_REGISTER_AUTOADDRESS = 0x40; +static const uint8_t TM1638_REGISTER_READBUTTONS = 0x42; +static const uint8_t TM1638_REGISTER_DISPLAYOFF = 0x80; +static const uint8_t TM1638_REGISTER_DISPLAYON = 0x88; +static const uint8_t TM1638_REGISTER_7SEG_0 = 0xC0; +static const uint8_t TM1638_REGISTER_LED_0 = 0xC1; +static const uint8_t TM1638_UNKNOWN_CHAR = 0b11111111; + +static const uint8_t TM1638_SHIFT_DELAY = 4; // clock pause between commands, default 4ms + +void TM1638Component::setup() { + ESP_LOGD(TAG, "Setting up TM1638..."); + + this->clk_pin_->setup(); // OUTPUT + this->dio_pin_->setup(); // OUTPUT + this->stb_pin_->setup(); // OUTPUT + + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->clk_pin_->digital_write(false); + this->dio_pin_->digital_write(false); + this->stb_pin_->digital_write(false); + + this->set_intensity(intensity_); + + this->reset_(); // all LEDs off + + for (uint8_t i = 0; i < 8; i++) // zero fill print buffer + this->buffer_[i] = 0; +} + +void TM1638Component::dump_config() { + ESP_LOGCONFIG(TAG, "TM1638:"); + ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); + LOG_PIN(" CLK Pin: ", this->clk_pin_); + LOG_PIN(" DIO Pin: ", this->dio_pin_); + LOG_PIN(" STB Pin: ", this->stb_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void TM1638Component::loop() { + if (this->listeners_.empty()) + return; + + uint8_t keys = this->get_keys(); + for (auto &listener : this->listeners_) + listener->keys_update(keys); +} + +uint8_t TM1638Component::get_keys() { + uint8_t buttons = 0; + + this->stb_pin_->digital_write(false); + + this->shift_out_(TM1638_REGISTER_READBUTTONS); + + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); + + delayMicroseconds(10); + + for (uint8_t i = 0; i < 4; i++) { // read the 4 button registers + uint8_t v = this->shift_in_(); + buttons |= v << i; // shift bits to correct slots in the byte + } + + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->stb_pin_->digital_write(true); + + return buttons; +} + +void TM1638Component::update() { // this is called at the interval specified in the config.yaml + if (this->writer_.has_value()) { + (*this->writer_)(*this); + } + + this->display(); +} + +float TM1638Component::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void TM1638Component::display() { + for (uint8_t i = 0; i < 8; i++) { + this->set_7seg_(i, buffer_[i]); + } +} + +void TM1638Component::reset_() { + uint8_t num_commands = 16; // 16 addresses, 8 for 7seg and 8 for LEDs + uint8_t commands[num_commands]; + + for (uint8_t i = 0; i < num_commands; i++) { + commands[i] = 0; + } + + this->send_command_sequence_(commands, num_commands, TM1638_REGISTER_7SEG_0); +} + +/////////////// LEDs ///////////////// + +void TM1638Component::set_led(int led_pos, bool led_on_off) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2]; + + commands[0] = TM1638_REGISTER_LED_0 + (led_pos << 1); + commands[1] = led_on_off; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_7seg_(int seg_pos, uint8_t seg_bits) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2] = {}; + + commands[0] = TM1638_REGISTER_7SEG_0 + (seg_pos << 1); + commands[1] = seg_bits; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_intensity(uint8_t brightness_level) { + this->intensity_ = brightness_level; + + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + if (brightness_level > 0) { + this->send_command_((uint8_t)(TM1638_REGISTER_DISPLAYON | intensity_)); + } else { + this->send_command_(TM1638_REGISTER_DISPLAYOFF); + } +} + +/////////////// DISPLAY PRINT ///////////////// + +uint8_t TM1638Component::print(uint8_t start_pos, const char *str) { + uint8_t pos = start_pos; + + bool last_was_dot = false; + + for (; *str != '\0'; str++) { + uint8_t data = TM1638_UNKNOWN_CHAR; + + if (*str >= ' ' && *str <= '~') { + data = progmem_read_byte(&TM1638Translation::SEVEN_SEG[*str - 32]); // subract 32 to account for ASCII offset + } else if (data == TM1638_UNKNOWN_CHAR) { + ESP_LOGW(TAG, "Encountered character '%c' with no TM1638 representation while translating string!", *str); + } + + if (*str == '.') // handle dots + { + if (pos != start_pos && + !last_was_dot) // if we are not at the first position, backup by one unless last char was a dot + { + pos--; + } + this->buffer_[pos] |= 0b10000000; // turn on the dot on the previous position + last_was_dot = true; // set a bit in case the next chracter is also a dot + } else // if not a dot, then just write the character to display + { + if (pos >= 8) { + ESP_LOGI(TAG, "TM1638 String is too long for the display!"); + break; + } + this->buffer_[pos] = data; + last_was_dot = false; // clear dot tracking bit + } + + pos++; + } + return pos - start_pos; +} + +/////////////// PRINT ///////////////// + +uint8_t TM1638Component::print(const char *str) { return this->print(0, str); } + +uint8_t TM1638Component::printf(uint8_t pos, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(buffer); + return 0; +} + +#ifdef USE_TIME +uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::ESPTime time) { + char buffer[64]; + size_t ret = time.strftime(buffer, sizeof(buffer), format); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } +#endif + +//////////////// SPI //////////////// + +void TM1638Component::send_command_(uint8_t value) { + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->digital_write(false); + this->shift_out_(value); + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_commands_(uint8_t const commands[], uint8_t num_commands) { + this->stb_pin_->digital_write(false); + + for (uint8_t i = 0; i < num_commands; i++) { + uint8_t command = commands[i]; + this->shift_out_(command); + } + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_command_leave_open_(uint8_t value) { + this->stb_pin_->digital_write(false); + this->shift_out_(value); +} + +void TM1638Component::send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address) { + this->send_command_(TM1638_REGISTER_AUTOADDRESS); + this->send_command_leave_open_(starting_address); + + for (uint8_t i = 0; i < num_commands; i++) { + this->shift_out_(commands[i]); + } + + this->stb_pin_->digital_write(true); +} + +uint8_t TM1638Component::shift_in_() { + uint8_t value = 0; + + for (int i = 0; i < 8; ++i) { + value |= dio_pin_->digital_read() << i; + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } + return value; +} + +void TM1638Component::shift_out_(uint8_t val) { + for (int i = 0; i < 8; i++) { + this->dio_pin_->digital_write((val & (1 << i))); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h new file mode 100644 index 0000000000..44160ad227 --- /dev/null +++ b/esphome/components/tm1638/tm1638.h @@ -0,0 +1,81 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + +namespace esphome { +namespace tm1638 { + +class KeyListener { + public: + virtual void keys_update(uint8_t keys){}; +}; + +class TM1638Component; + +using tm1638_writer_t = std::function; + +class TM1638Component : public PollingComponent { + public: + void set_writer(tm1638_writer_t &&writer) { this->writer_ = writer; } + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + void set_intensity(uint8_t brightness_level); + void display(); + + void set_clk_pin(GPIOPin *pin) { this->clk_pin_ = pin; } + void set_dio_pin(GPIOPin *pin) { this->dio_pin_ = pin; } + void set_stb_pin(GPIOPin *pin) { this->stb_pin_ = pin; } + + void register_listener(KeyListener *listener) { this->listeners_.push_back(listener); } + + /// Evaluate the printf-format and print the result at the given position. + uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4))); + /// Evaluate the printf-format and print the result at position 0. + uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + + /// Print `str` at the given position. + uint8_t print(uint8_t pos, const char *str); + /// Print `str` at position 0. + uint8_t print(const char *str); + + void loop() override; + uint8_t get_keys(); + +#ifdef USE_TIME + /// Evaluate the strftime-format and print the result at the given position. + uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + /// Evaluate the strftime-format and print the result at position 0. + uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); +#endif + + void set_led(int led_pos, bool led_on_off); + + protected: + void set_7seg_(int seg_pos, uint8_t seg_bits); + void send_command_(uint8_t value); + void send_command_leave_open_(uint8_t value); + void send_commands_(uint8_t const commands[], uint8_t num_commands); + void send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address); + void shift_out_(uint8_t value); + void reset_(); + uint8_t shift_in_(); + uint8_t intensity_{}; /// brghtness of the display 0 through 7 + GPIOPin *clk_pin_; + GPIOPin *stb_pin_; + GPIOPin *dio_pin_; + uint8_t *buffer_ = new uint8_t[8]; + optional writer_{}; + std::vector listeners_{}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 51c07ab666..5153efbbe0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -334,6 +334,7 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LED = "led" CONF_LEGEND = "legend" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -653,6 +654,7 @@ CONF_STATE_CLASS = "state_class" CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" +CONF_STB_PIN = "stb_pin" CONF_STEP = "step" CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" diff --git a/tests/test5.yaml b/tests/test5.yaml index 3b6862a4d4..7fc20c452f 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -80,6 +80,91 @@ binary_sensor: bitmask: 0x80 # (bit 8) lambda: "return x;" + - platform: tm1638 + id: Button0 + key: 0 + filters: + - delayed_on: 10ms + on_press: + then: + - switch.turn_on: Led0 + on_release: + then: + - switch.turn_off: Led0 + + - platform: tm1638 + id: Button1 + key: 1 + on_press: + then: + - switch.turn_on: Led1 + on_release: + then: + - switch.turn_off: Led1 + + - platform: tm1638 + id: Button2 + key: 2 + on_press: + then: + - switch.turn_on: Led2 + on_release: + then: + - switch.turn_off: Led2 + + - platform: tm1638 + id: Button3 + key: 3 + on_press: + then: + - switch.turn_on: Led3 + on_release: + then: + - switch.turn_off: Led3 + + - platform: tm1638 + id: Button4 + key: 4 + on_press: + then: + - output.turn_on: Led4 + on_release: + then: + - output.turn_off: Led4 + + - platform: tm1638 + id: Button5 + key: 5 + on_press: + then: + - output.turn_on: Led5 + on_release: + then: + - output.turn_off: Led5 + + - platform: tm1638 + id: Button6 + key: 6 + on_press: + then: + - output.turn_on: Led6 + on_release: + then: + - output.turn_off: Led6 + + - platform: tm1638 + id: Button7 + key: 7 + on_press: + then: + - output.turn_on: Led7 + on_release: + then: + - output.turn_off: Led7 + + + + tlc5947: data_pin: GPIO12 clock_pin: GPIO14 @@ -106,6 +191,22 @@ output: address: 0x9001 value_type: U_WORD + - platform: tm1638 + id: Led4 + led: 4 + + - platform: tm1638 + id: Led5 + led: 5 + + - platform: tm1638 + id: Led6 + led: 6 + + - platform: tm1638 + id: Led7 + led: 7 + demo: esp32_ble: @@ -354,3 +455,35 @@ switch: register_type: coil address: 2 bitmask: 1 + + - platform: tm1638 + id: Led0 + led: 0 + name: TM1638Led0 + + - platform: tm1638 + id: Led1 + led: 1 + name: TM1638Led1 + + - platform: tm1638 + id: Led2 + led: 2 + name: TM1638Led2 + + - platform: tm1638 + id: Led3 + led: 3 + name: TM1638Led3 + +display: + - platform: tm1638 + id: primarydisplay + stb_pin: 5 #TM1638 STB + clk_pin: 18 #TM1638 CLK + dio_pin: 23 #TM1638 DIO + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); +