Files
esphome-samsung-vfd/components/samsung_vfd/samsung_vfd.h
T
mangavalkandClaude Opus 5.5 3fcd15ad86 Add photos and datasheet; correct wiring and protocol docs
The 20L203 input is RS-232 (reference build uses a MAX3232), it runs on
5-24 V, and it boots in non-scrolling overwrite mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:44:07 +01:00

125 lines
4.2 KiB
C++

#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 <string>
#include <vector>
namespace esphome {
namespace samsung_vfd {
static const char *const TAG = "samsung_vfd";
// Control bytes, see section 5.4 of docs/20L203DA12-datasheet.pdf.
static const uint8_t CMD_ESC = 0x1B;
static const uint8_t CMD_INIT = 0x40; // ESC @ -> reset / init display
static const uint8_t CMD_CLEAR = 0x0C; // FF -> clear screen (cursor to home)
static const uint8_t CMD_HOME = 0x0B; // VT -> cursor home
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; }
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) { send_newlines_ = v; }
uint8_t cols() const { return cols_; }
uint8_t rows() const { return rows_; }
/// 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);
void clear();
void home();
std::string line_text(uint8_t line) const {
return line < lines_.size() ? lines_[line] : std::string();
}
protected:
void init_display_();
void resize_lines_();
/// Fit `text` (already single-byte) 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);
/// Map UTF-8 to one byte per character: accented Latin-1 letters lose their
/// accent, control characters become spaces, anything else becomes '?'.
static std::string sanitize_(const std::string &text, bool keep_newlines);
uint8_t cols_{20};
uint8_t rows_{2};
bool send_newlines_{false};
std::vector<std::string> lines_;
std::string last_payload_;
};
// ---------- actions ----------
template<typename... Ts> class PrintAction : public Action<Ts...>, public Parented<SamsungVfd> {
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<uint8_t>(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<typename... Ts> class PrintfAction : public Action<Ts...>, public Parented<SamsungVfd> {
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<uint8_t>(this->line_.value(x...)), text, centered);
else
this->parent_->print(text, centered);
}
protected:
time::RealTimeClock *rtc_{nullptr};
};
template<typename... Ts> class ClearAction : public Action<Ts...>, public Parented<SamsungVfd> {
public:
void play(const Ts &...x) override { this->parent_->clear(); }
};
template<typename... Ts> class HomeAction : public Action<Ts...>, public Parented<SamsungVfd> {
public:
void play(const Ts &...x) override { this->parent_->home(); }
};
} // namespace samsung_vfd
} // namespace esphome