#pragma once #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" #include "esphome/components/time/real_time_clock.h" #include "esphome/components/uart/uart.h" #include #include #include namespace esphome { namespace samsung_vfd { static const char *const TAG = "samsung_vfd"; // Control codes, see section 5.4 of docs/20L203DA12-datasheet.pdf. static const uint8_t CMD_BS = 0x08; // cursor left static const uint8_t CMD_HT = 0x09; // cursor right static const uint8_t CMD_LF = 0x0A; // cursor down static const uint8_t CMD_HOME = 0x0B; // cursor to top-left static const uint8_t CMD_CLEAR = 0x0C; // clear screen, cursor home static const uint8_t CMD_CR = 0x0D; // cursor to start of line static const uint8_t CMD_CAN = 0x18; // clear cursor line static const uint8_t CMD_ESC = 0x1B; static const uint8_t CMD_US = 0x1F; // Second byte after ESC. static const uint8_t ESC_INIT = 0x40; // ESC @ initialise static const uint8_t ESC_USER_FONT_SET = 0x25; // ESC % n select/cancel user font set static const uint8_t ESC_DEFINE_CHAR = 0x26; // ESC & ... define user characters static const uint8_t ESC_CANCEL_CHAR = 0x3F; // ESC ? n cancel a user character static const uint8_t ESC_PERIPHERAL = 0x3D; // ESC = n select peripheral device static const uint8_t ESC_INTL_CHARSET = 0x52; // ESC R n international character set static const uint8_t ESC_CODE_TABLE = 0x74; // ESC t n CG-ROM table for 0x80..0xFF // Second byte after US. static const uint8_t US_MODE_OVERWRITE = 0x01; // US MD1 static const uint8_t US_MODE_VSCROLL = 0x02; // US MD2 static const uint8_t US_MODE_HSCROLL = 0x03; // US MD3 static const uint8_t US_CURSOR_UP = 0x0A; // US LF static const uint8_t US_LINE_END = 0x0D; // US CR static const uint8_t US_ANNUNCIATOR = 0x23; // US # n m static const uint8_t US_SET_CURSOR = 0x24; // US $ n m static const uint8_t US_SELF_TEST = 0x40; // US @ static const uint8_t US_BOTTOM = 0x42; // US B static const uint8_t US_BLINK = 0x45; // US E n static const uint8_t US_BRIGHTNESS = 0x58; // US X n static const uint8_t BLINK_OFF = 0x00; static const uint8_t BLINK_BLANK = 0xFF; // US E FF: screen off, contents kept static const uint8_t MAX_USER_CHARS = 4; // per datasheet 5.4.14 enum CodeTable : uint8_t { CODE_TABLE_PC437 = 0, CODE_TABLE_PC850 = 1, CODE_TABLE_PC866 = 2, CODE_TABLE_PC737 = 3, CODE_TABLE_PC775 = 4, CODE_TABLE_CP10 = 5, CODE_TABLE_PC864 = 6, CODE_TABLE_PC852 = 7, }; enum InternationalCharset : uint8_t { CHARSET_USA = 0, CHARSET_FRANCE = 1, CHARSET_GERMANY = 2, CHARSET_UK = 3, CHARSET_DENMARK1 = 4, CHARSET_SWEDEN = 5, CHARSET_ITALY = 6, CHARSET_SPAIN1 = 7, CHARSET_JAPAN = 8, CHARSET_NORWAY = 9, CHARSET_DENMARK2 = 10, }; enum DisplayMode : uint8_t { MODE_OVERWRITE = US_MODE_OVERWRITE, MODE_VERTICAL_SCROLL = US_MODE_VSCROLL, MODE_HORIZONTAL_SCROLL = US_MODE_HSCROLL, }; struct UserChar { uint8_t code; std::array columns; // one byte per column, bit 6 = top row, bit 0 = bottom row }; class SamsungVfd : public Component, public uart::UARTDevice { public: void setup() override; void dump_config() override; // Before the default (DATA) priority, so restored entity values can be shown. float get_setup_priority() const override { return setup_priority::HARDWARE; } // ---------- configuration (before setup) ---------- void set_size(uint8_t cols, uint8_t rows) { this->cols_ = cols == 0 ? 20 : cols; this->rows_ = rows == 0 ? 2 : rows; } void set_send_newlines(bool v) { this->send_newlines_ = v; } void set_initial_brightness(float b) { this->brightness_level_ = brightness_to_level_(b); } void set_initial_code_table(CodeTable t) { this->code_table_ = t; } void set_initial_charset(InternationalCharset c) { this->charset_ = c; } void add_user_char(uint8_t code, uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4) { this->user_chars_.push_back(UserChar{code, {c0, c1, c2, c3, c4}}); } uint8_t cols() const { return this->cols_; } uint8_t rows() const { return this->rows_; } // ---------- buffered text (the normal way to use the display) ---------- /// Replace the whole screen with `text`, wrapping it over the available lines. void print(const std::string &text, bool centered = false); /// Replace a single line (0-based). Text is truncated/padded to the column count. void print_line(uint8_t line, const std::string &text, bool centered = false); /// Clear the screen (and the buffer). void clear(); std::string line_text(uint8_t line) const { return line < this->lines_.size() ? this->lines_[line] : std::string(); } // ---------- display settings (kept and re-applied after reinit()) ---------- /// 0.0..1.0, rounded to the display's four levels (40/60/80/100 %). void set_brightness(float brightness); /// Blink the whole screen: `ms` on, `ms` off (13 ms steps). 0 = no blinking. void set_blink(uint32_t ms); /// Blank the screen without losing its contents, and bring it back. void turn_off(); void turn_on(); bool is_on() const { return this->on_; } /// Triangle under column `column` (1-based); 0 = all of them. void set_annunciator(uint8_t column, bool state); void set_code_table(CodeTable table); void set_international_charset(InternationalCharset charset); /// Define (or redefine) a user character at `code` (0x20..0x7E). Up to 4. void define_char(uint8_t code, const std::array &columns); /// Remove a user character; `code` shows its normal glyph again. void cancel_char(uint8_t code); /// Reset the display (ESC @), re-apply all settings above and redraw. /// Use this when the display was power-cycled separately from the ESP. void reinit(); // ---------- raw cursor and mode commands ---------- // The buffered text functions assume overwrite mode and redraw from the // top-left. Writing text after these commands makes the next print redraw // the whole screen. void home(); void cursor_left(); void cursor_right(); void cursor_down(); void cursor_up(); void cursor_line_start(); void cursor_line_end(); void cursor_bottom(); /// 0-based column and line. void set_cursor(uint8_t column, uint8_t line); void clear_cursor_line(); void set_display_mode(DisplayMode mode); /// 1 = this display off (pass-through only), 2 = this display (default), 3 = both. void select_peripheral(uint8_t n); /// Runs for about 20 s; the display ignores input meanwhile. void self_test(); /// Write UTF-8 text at the cursor, converted like print() does. void write_text(const std::string &text); /// Send bytes unchanged. void write_raw(const std::vector &data); void write_raw(const uint8_t *data, size_t len); /// Convert UTF-8 to display bytes for the current code table. std::string encode(const std::string &text, bool keep_newlines = false) const; protected: void init_display_(); void apply_settings_(); void send_user_chars_(); void send_annunciators_(); void resize_lines_(); /// Fit `text` (already encoded) into one line: truncate or pad, optionally centred. std::string fit_(const std::string &text, bool centered) const; /// Rebuild the wire payload from the shadow buffer and send it only if it changed. void flush_(); void write_str_(const std::string &s); void send_esc_(uint8_t cmd); void send_esc_(uint8_t cmd, uint8_t arg); void send_us_(uint8_t cmd); void send_us_(uint8_t cmd, uint8_t arg); void invalidate_() { this->last_payload_.clear(); } char encode_codepoint_(uint32_t cp) const; static uint8_t brightness_to_level_(float b); uint8_t cols_{20}; uint8_t rows_{2}; bool send_newlines_{false}; std::vector lines_; std::string last_payload_; uint8_t brightness_level_{4}; uint8_t blink_{BLINK_OFF}; bool on_{true}; uint32_t annunciators_{0}; // bit n = column n (1..20) CodeTable code_table_{CODE_TABLE_PC437}; InternationalCharset charset_{CHARSET_USA}; DisplayMode mode_{MODE_OVERWRITE}; std::vector user_chars_; }; // ---------- actions ---------- template class PrintAction : public Action, public Parented { public: TEMPLATABLE_VALUE(std::string, text) TEMPLATABLE_VALUE(int, line) TEMPLATABLE_VALUE(bool, centered) void play(const Ts &...x) override { std::string text = this->text_.value(x...); bool centered = this->centered_.has_value() && this->centered_.value(x...); if (this->line_.has_value()) this->parent_->print_line(static_cast(this->line_.value(x...)), text, centered); else this->parent_->print(text, centered); } }; /// Like print, but formats the current time with strftime (e.g. "%H:%M:%S"). template class PrintfAction : public Action, public Parented { public: TEMPLATABLE_VALUE(std::string, format) TEMPLATABLE_VALUE(int, line) TEMPLATABLE_VALUE(bool, centered) void set_time(time::RealTimeClock *rtc) { this->rtc_ = rtc; } void play(const Ts &...x) override { auto now = this->rtc_->now(); if (!now.is_valid()) return; // clock not synchronised yet, leave the screen as it is std::string text = now.strftime(this->format_.value(x...)); bool centered = this->centered_.has_value() && this->centered_.value(x...); if (this->line_.has_value()) this->parent_->print_line(static_cast(this->line_.value(x...)), text, centered); else this->parent_->print(text, centered); } protected: time::RealTimeClock *rtc_{nullptr}; }; template class ClearAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->clear(); } }; template class HomeAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->home(); } }; template class SetBrightnessAction : public Action, public Parented { public: TEMPLATABLE_VALUE(float, brightness) void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); } }; template class SetBlinkAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint32_t, interval) void play(const Ts &...x) override { this->parent_->set_blink(this->interval_.value(x...)); } }; template class TurnOnAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->turn_on(); } }; template class TurnOffAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->turn_off(); } }; template class SetAnnunciatorAction : public Action, public Parented { public: TEMPLATABLE_VALUE(int, column) TEMPLATABLE_VALUE(bool, state) void play(const Ts &...x) override { this->parent_->set_annunciator(static_cast(this->column_.value(x...)), this->state_.value(x...)); } }; template class SetCursorAction : public Action, public Parented { public: TEMPLATABLE_VALUE(int, column) TEMPLATABLE_VALUE(int, line) void play(const Ts &...x) override { this->parent_->set_cursor(static_cast(this->column_.value(x...)), static_cast(this->line_.value(x...))); } }; template class SetDisplayModeAction : public Action, public Parented { public: void set_mode(DisplayMode mode) { this->mode_ = mode; } void play(const Ts &...x) override { this->parent_->set_display_mode(this->mode_); } protected: DisplayMode mode_{MODE_OVERWRITE}; }; template class WriteAction : public Action, public Parented { public: TEMPLATABLE_VALUE(std::string, text) void set_data(const std::vector &data) { this->data_ = data; } void play(const Ts &...x) override { if (this->text_.has_value()) this->parent_->write_text(this->text_.value(x...)); else this->parent_->write_raw(this->data_); } protected: std::vector data_; }; template class ReinitAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->reinit(); } }; template class SelfTestAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->self_test(); } }; } // namespace samsung_vfd } // namespace esphome