Support every datasheet command and map text to the CG-ROM fonts
- Brightness, blink, screen on/off, annunciators, cursor control, display modes, user-defined characters, code table, international charset, peripheral select, self-test, raw writes and reinit - UTF-8 text is encoded with the selected code table (tables generated from Python codecs by tools/gen_charmaps.py), falling back to accent-stripping - Settings survive reinit and the self-test - Example gains brightness select and display switch, and uses the git source on mangavalk.com Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3fcd15ad86
commit
95686a3608
8 files changed
+1322
-148
No files matched your search
@@ -2,25 +2,154 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import time as time_, uart
|
||||
from esphome.const import CONF_ID, CONF_TEXT, CONF_TIME_ID
|
||||
from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_DATA,
|
||||
CONF_ID,
|
||||
CONF_INTERVAL,
|
||||
CONF_MODE,
|
||||
CONF_STATE,
|
||||
CONF_TEXT,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart", "time"]
|
||||
|
||||
CONF_COLS = "cols"
|
||||
CONF_COLUMN = "column"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_LINE = "line"
|
||||
CONF_FORMAT = "format"
|
||||
CONF_CENTERED = "centered"
|
||||
CONF_SEND_NEWLINES = "send_newlines"
|
||||
CONF_CODE_TABLE = "code_table"
|
||||
CONF_INTERNATIONAL_CHARSET = "international_charset"
|
||||
CONF_USER_CHARACTERS = "user_characters"
|
||||
CONF_CODE = "code"
|
||||
CONF_PATTERN = "pattern"
|
||||
|
||||
samsung_vfd_ns = cg.esphome_ns.namespace("samsung_vfd")
|
||||
|
||||
SamsungVfd = samsung_vfd_ns.class_("SamsungVfd", cg.Component, uart.UARTDevice)
|
||||
|
||||
CodeTable = samsung_vfd_ns.enum("CodeTable")
|
||||
CODE_TABLES = {
|
||||
"PC437": CodeTable.CODE_TABLE_PC437,
|
||||
"PC850": CodeTable.CODE_TABLE_PC850,
|
||||
"PC866": CodeTable.CODE_TABLE_PC866,
|
||||
"PC737": CodeTable.CODE_TABLE_PC737,
|
||||
"PC775": CodeTable.CODE_TABLE_PC775,
|
||||
"CP10": CodeTable.CODE_TABLE_CP10,
|
||||
"PC864": CodeTable.CODE_TABLE_PC864,
|
||||
"PC852": CodeTable.CODE_TABLE_PC852,
|
||||
}
|
||||
|
||||
InternationalCharset = samsung_vfd_ns.enum("InternationalCharset")
|
||||
CHARSETS = {
|
||||
"USA": InternationalCharset.CHARSET_USA,
|
||||
"FRANCE": InternationalCharset.CHARSET_FRANCE,
|
||||
"GERMANY": InternationalCharset.CHARSET_GERMANY,
|
||||
"UK": InternationalCharset.CHARSET_UK,
|
||||
"DENMARK1": InternationalCharset.CHARSET_DENMARK1,
|
||||
"SWEDEN": InternationalCharset.CHARSET_SWEDEN,
|
||||
"ITALY": InternationalCharset.CHARSET_ITALY,
|
||||
"SPAIN1": InternationalCharset.CHARSET_SPAIN1,
|
||||
"JAPAN": InternationalCharset.CHARSET_JAPAN,
|
||||
"NORWAY": InternationalCharset.CHARSET_NORWAY,
|
||||
"DENMARK2": InternationalCharset.CHARSET_DENMARK2,
|
||||
}
|
||||
|
||||
DisplayMode = samsung_vfd_ns.enum("DisplayMode")
|
||||
DISPLAY_MODES = {
|
||||
"OVERWRITE": DisplayMode.MODE_OVERWRITE,
|
||||
"VERTICAL_SCROLL": DisplayMode.MODE_VERTICAL_SCROLL,
|
||||
"HORIZONTAL_SCROLL": DisplayMode.MODE_HORIZONTAL_SCROLL,
|
||||
}
|
||||
|
||||
PrintAction = samsung_vfd_ns.class_("PrintAction", automation.Action)
|
||||
PrintfAction = samsung_vfd_ns.class_("PrintfAction", automation.Action)
|
||||
ClearAction = samsung_vfd_ns.class_("ClearAction", automation.Action)
|
||||
HomeAction = samsung_vfd_ns.class_("HomeAction", automation.Action)
|
||||
SetBrightnessAction = samsung_vfd_ns.class_("SetBrightnessAction", automation.Action)
|
||||
SetBlinkAction = samsung_vfd_ns.class_("SetBlinkAction", automation.Action)
|
||||
TurnOnAction = samsung_vfd_ns.class_("TurnOnAction", automation.Action)
|
||||
TurnOffAction = samsung_vfd_ns.class_("TurnOffAction", automation.Action)
|
||||
SetAnnunciatorAction = samsung_vfd_ns.class_("SetAnnunciatorAction", automation.Action)
|
||||
SetCursorAction = samsung_vfd_ns.class_("SetCursorAction", automation.Action)
|
||||
SetDisplayModeAction = samsung_vfd_ns.class_("SetDisplayModeAction", automation.Action)
|
||||
WriteAction = samsung_vfd_ns.class_("WriteAction", automation.Action)
|
||||
ReinitAction = samsung_vfd_ns.class_("ReinitAction", automation.Action)
|
||||
SelfTestAction = samsung_vfd_ns.class_("SelfTestAction", automation.Action)
|
||||
|
||||
PIXEL_ON = "#X1*"
|
||||
PIXEL_OFF = ".-0 _"
|
||||
|
||||
|
||||
def user_char_code(value):
|
||||
if isinstance(value, str):
|
||||
if len(value) != 1:
|
||||
raise cv.Invalid("code must be a single character or a number")
|
||||
value = ord(value)
|
||||
value = cv.int_(value)
|
||||
if not 0x20 <= value <= 0x7E:
|
||||
raise cv.Invalid("code must be between 0x20 and 0x7E (a printable ASCII character)")
|
||||
return value
|
||||
|
||||
|
||||
def user_char_pattern(value):
|
||||
"""7 strings of 5 pixels, top row first, e.g. '.###.'."""
|
||||
value = cv.ensure_list(cv.string_strict)(value)
|
||||
if len(value) != 7:
|
||||
raise cv.Invalid("pattern must have exactly 7 rows")
|
||||
for row in value:
|
||||
if len(row) != 5:
|
||||
raise cv.Invalid(f"pattern row '{row}' must be exactly 5 pixels wide")
|
||||
for ch in row:
|
||||
if ch not in PIXEL_ON + PIXEL_OFF:
|
||||
raise cv.Invalid(
|
||||
f"pattern row '{row}': use one of '{PIXEL_ON}' for on and '{PIXEL_OFF}' for off"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def pattern_to_columns(pattern):
|
||||
# One byte per column; bit 6 = top row, bit 0 = bottom row (datasheet 5.4.14).
|
||||
cols = []
|
||||
for c in range(5):
|
||||
byte = 0
|
||||
for r, row in enumerate(pattern):
|
||||
if row[c] in PIXEL_ON:
|
||||
byte |= 1 << (6 - r)
|
||||
cols.append(byte)
|
||||
return cols
|
||||
|
||||
|
||||
def unique_codes(value):
|
||||
codes = [uc[CONF_CODE] for uc in value]
|
||||
for code in codes:
|
||||
if codes.count(code) > 1:
|
||||
raise cv.Invalid(f"user character code 0x{code:02X} defined twice")
|
||||
return value
|
||||
|
||||
|
||||
def validate_write(config):
|
||||
if (CONF_TEXT in config) == (CONF_DATA in config):
|
||||
raise cv.Invalid("specify exactly one of 'text' or 'data'")
|
||||
return config
|
||||
|
||||
|
||||
def raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return list(value.encode("latin-1"))
|
||||
return cv.ensure_list(cv.hex_uint8_t)(value)
|
||||
|
||||
|
||||
USER_CHAR_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CODE): user_char_code,
|
||||
cv.Required(CONF_PATTERN): user_char_pattern,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -31,15 +160,21 @@ CONFIG_SCHEMA = (
|
||||
# Off by default: lines are padded to exactly `cols` characters and the
|
||||
# display wraps to the next line by itself.
|
||||
cv.Optional(CONF_SEND_NEWLINES, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BRIGHTNESS, default="100%"): cv.percentage,
|
||||
cv.Optional(CONF_CODE_TABLE, default="PC437"): cv.enum(CODE_TABLES, upper=True),
|
||||
cv.Optional(CONF_INTERNATIONAL_CHARSET, default="USA"): cv.enum(CHARSETS, upper=True),
|
||||
cv.Optional(CONF_USER_CHARACTERS, default=[]): cv.All(
|
||||
cv.ensure_list(USER_CHAR_SCHEMA), cv.Length(max=4), unique_codes
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"samsung_vfd", baud_rate=9600, require_tx=True
|
||||
)
|
||||
# Baud rate and parity must match the display's jumpers, so they are left to
|
||||
# the uart: block.
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("samsung_vfd", require_tx=True)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -48,6 +183,35 @@ async def to_code(config):
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_size(config[CONF_COLS], config[CONF_ROWS]))
|
||||
cg.add(var.set_send_newlines(config[CONF_SEND_NEWLINES]))
|
||||
cg.add(var.set_initial_brightness(config[CONF_BRIGHTNESS]))
|
||||
cg.add(var.set_initial_code_table(config[CONF_CODE_TABLE]))
|
||||
cg.add(var.set_initial_charset(config[CONF_INTERNATIONAL_CHARSET]))
|
||||
for uc in config[CONF_USER_CHARACTERS]:
|
||||
cg.add(var.add_user_char(uc[CONF_CODE], *pattern_to_columns(uc[CONF_PATTERN])))
|
||||
|
||||
|
||||
# ---------- actions ----------
|
||||
|
||||
SIMPLE_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(SamsungVfd)})
|
||||
|
||||
|
||||
async def simple_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
for _name, _cls in (
|
||||
("clear", ClearAction),
|
||||
("home", HomeAction),
|
||||
("turn_on", TurnOnAction),
|
||||
("turn_off", TurnOffAction),
|
||||
("reinit", ReinitAction),
|
||||
("self_test", SelfTestAction),
|
||||
):
|
||||
automation.register_action(
|
||||
f"samsung_vfd.{_name}", _cls, SIMPLE_ACTION_SCHEMA, synchronous=True
|
||||
)(simple_action_to_code)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -55,7 +219,7 @@ async def to_code(config):
|
||||
PrintAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(SamsungVfd),
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_TEXT): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_LINE): cv.templatable(cv.int_range(min=0, max=7)),
|
||||
cv.Optional(CONF_CENTERED): cv.templatable(cv.boolean),
|
||||
@@ -79,7 +243,7 @@ async def samsung_vfd_print_to_code(config, action_id, template_arg, args):
|
||||
PrintfAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(SamsungVfd),
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_TIME_ID): cv.use_id(time_.RealTimeClock),
|
||||
cv.Required(CONF_FORMAT): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_LINE): cv.templatable(cv.int_range(min=0, max=7)),
|
||||
@@ -101,24 +265,121 @@ async def samsung_vfd_printf_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.clear",
|
||||
ClearAction,
|
||||
cv.Schema({cv.Required(CONF_ID): cv.use_id(SamsungVfd)}),
|
||||
"samsung_vfd.set_brightness",
|
||||
SetBrightnessAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||
},
|
||||
key=CONF_BRIGHTNESS,
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_clear_to_code(config, action_id, template_arg, args):
|
||||
async def samsung_vfd_set_brightness_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_brightness(await cg.templatable(config[CONF_BRIGHTNESS], args, cg.float_)))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.home",
|
||||
HomeAction,
|
||||
cv.Schema({cv.Required(CONF_ID): cv.use_id(SamsungVfd)}),
|
||||
"samsung_vfd.set_blink",
|
||||
SetBlinkAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_INTERVAL): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_home_to_code(config, action_id, template_arg, args):
|
||||
async def samsung_vfd_set_blink_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_interval(await cg.templatable(config[CONF_INTERVAL], args, cg.uint32)))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.set_annunciator",
|
||||
SetAnnunciatorAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_COLUMN): cv.templatable(cv.int_range(min=0, max=20)),
|
||||
cv.Required(CONF_STATE): cv.templatable(cv.boolean),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_set_annunciator_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_column(await cg.templatable(config[CONF_COLUMN], args, cg.int_)))
|
||||
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.bool_)))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.set_cursor",
|
||||
SetCursorAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_COLUMN): cv.templatable(cv.int_range(min=0, max=79)),
|
||||
cv.Required(CONF_LINE): cv.templatable(cv.int_range(min=0, max=7)),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_set_cursor_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_column(await cg.templatable(config[CONF_COLUMN], args, cg.int_)))
|
||||
cg.add(var.set_line(await cg.templatable(config[CONF_LINE], args, cg.int_)))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.set_display_mode",
|
||||
SetDisplayModeAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Required(CONF_MODE): cv.enum(DISPLAY_MODES, upper=True),
|
||||
},
|
||||
key=CONF_MODE,
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_set_display_mode_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"samsung_vfd.write",
|
||||
WriteAction,
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SamsungVfd),
|
||||
cv.Optional(CONF_TEXT): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA): raw_data,
|
||||
}
|
||||
),
|
||||
validate_write,
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def samsung_vfd_write_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
if CONF_TEXT in config:
|
||||
cg.add(var.set_text(await cg.templatable(config[CONF_TEXT], args, cg.std_string)))
|
||||
else:
|
||||
cg.add(var.set_data(config[CONF_DATA]))
|
||||
return var
|
||||
@@ -0,0 +1,162 @@
|
||||
// Generated by tools/gen_charmaps.py -- do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace samsung_vfd {
|
||||
|
||||
// PC437 (cp437), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC437[128] = {
|
||||
0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, // 0x80
|
||||
0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, // 0x88
|
||||
0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, // 0x90
|
||||
0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, // 0x98
|
||||
0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, // 0xA0
|
||||
0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // 0xB0
|
||||
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, // 0xC8
|
||||
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // 0xD0
|
||||
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, // 0xD8
|
||||
0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, // 0xE0
|
||||
0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, // 0xE8
|
||||
0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, // 0xF0
|
||||
0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// PC850 (cp850), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC850[128] = {
|
||||
0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, // 0x80
|
||||
0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, // 0x88
|
||||
0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, // 0x90
|
||||
0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, // 0x98
|
||||
0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, // 0xA0
|
||||
0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, // 0xB0
|
||||
0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, // 0xC8
|
||||
0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, // 0xD0
|
||||
0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, // 0xD8
|
||||
0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, // 0xE0
|
||||
0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, // 0xE8
|
||||
0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, // 0xF0
|
||||
0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// PC866 (cp866), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC866[128] = {
|
||||
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, // 0x80
|
||||
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, // 0x88
|
||||
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, // 0x90
|
||||
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, // 0x98
|
||||
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, // 0xA0
|
||||
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // 0xB0
|
||||
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, // 0xC8
|
||||
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // 0xD0
|
||||
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, // 0xD8
|
||||
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, // 0xE0
|
||||
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, // 0xE8
|
||||
0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, // 0xF0
|
||||
0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// PC737 (cp737), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC737[128] = {
|
||||
0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, // 0x80
|
||||
0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, // 0x88
|
||||
0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, // 0x90
|
||||
0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, // 0x98
|
||||
0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, // 0xA0
|
||||
0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // 0xB0
|
||||
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, // 0xC8
|
||||
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // 0xD0
|
||||
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, // 0xD8
|
||||
0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, // 0xE0
|
||||
0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E, // 0xE8
|
||||
0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, // 0xF0
|
||||
0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// PC775 (cp775), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC775[128] = {
|
||||
0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, // 0x80
|
||||
0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5, // 0x88
|
||||
0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, // 0x90
|
||||
0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4, // 0x98
|
||||
0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, // 0xA0
|
||||
0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, // 0xB0
|
||||
0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D, // 0xC8
|
||||
0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, // 0xD0
|
||||
0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, // 0xD8
|
||||
0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, // 0xE0
|
||||
0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019, // 0xE8
|
||||
0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, // 0xF0
|
||||
0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// PC864 (cp864), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC864[128] = {
|
||||
0x00B0, 0x00B7, 0x2219, 0x221A, 0x2592, 0x2500, 0x2502, 0x253C, // 0x80
|
||||
0x2524, 0x252C, 0x251C, 0x2534, 0x2510, 0x250C, 0x2514, 0x2518, // 0x88
|
||||
0x03B2, 0x221E, 0x03C6, 0x00B1, 0x00BD, 0x00BC, 0x2248, 0x00AB, // 0x90
|
||||
0x00BB, 0xFEF7, 0xFEF8, 0x0000, 0x0000, 0xFEFB, 0xFEFC, 0x0000, // 0x98
|
||||
0x00A0, 0x00AD, 0xFE82, 0x00A3, 0x00A4, 0xFE84, 0x0000, 0x0000, // 0xA0
|
||||
0xFE8E, 0xFE8F, 0xFE95, 0xFE99, 0x060C, 0xFE9D, 0xFEA1, 0xFEA5, // 0xA8
|
||||
0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, // 0xB0
|
||||
0x0668, 0x0669, 0xFED1, 0x061B, 0xFEB1, 0xFEB5, 0xFEB9, 0x061F, // 0xB8
|
||||
0x00A2, 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFECA, 0xFE8B, 0xFE8D, // 0xC0
|
||||
0xFE91, 0xFE93, 0xFE97, 0xFE9B, 0xFE9F, 0xFEA3, 0xFEA7, 0xFEA9, // 0xC8
|
||||
0xFEAB, 0xFEAD, 0xFEAF, 0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, 0xFEC1, // 0xD0
|
||||
0xFEC5, 0xFECB, 0xFECF, 0x00A6, 0x00AC, 0x00F7, 0x00D7, 0xFEC9, // 0xD8
|
||||
0x0640, 0xFED3, 0xFED7, 0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, 0xFEEB, // 0xE0
|
||||
0xFEED, 0xFEEF, 0xFEF3, 0xFEBD, 0xFECC, 0xFECE, 0xFECD, 0xFEE1, // 0xE8
|
||||
0xFE7D, 0x0651, 0xFEE5, 0xFEE9, 0xFEEC, 0xFEF0, 0xFEF2, 0xFED0, // 0xF0
|
||||
0xFED5, 0xFEF5, 0xFEF6, 0xFEDD, 0xFED9, 0xFEF1, 0x25A0, 0x0000, // 0xF8
|
||||
};
|
||||
|
||||
// PC852 (cp852), bytes 0x80..0xFF
|
||||
static const uint16_t CHARMAP_PC852[128] = {
|
||||
0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, // 0x80
|
||||
0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, // 0x88
|
||||
0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, // 0x90
|
||||
0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, // 0x98
|
||||
0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, // 0xA0
|
||||
0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, // 0xA8
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, // 0xB0
|
||||
0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, // 0xB8
|
||||
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, // 0xC0
|
||||
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, // 0xC8
|
||||
0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, // 0xD0
|
||||
0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, // 0xD8
|
||||
0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, // 0xE0
|
||||
0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, // 0xE8
|
||||
0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, // 0xF0
|
||||
0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0, // 0xF8
|
||||
};
|
||||
|
||||
// Indexed by CodeTable (the ESC t argument); nullptr = no mapping known.
|
||||
static const uint16_t *const CHARMAPS[8] = {
|
||||
CHARMAP_PC437, // PC437
|
||||
CHARMAP_PC850, // PC850
|
||||
CHARMAP_PC866, // PC866
|
||||
CHARMAP_PC737, // PC737
|
||||
CHARMAP_PC775, // PC775
|
||||
nullptr, // CP10
|
||||
CHARMAP_PC864, // PC864
|
||||
CHARMAP_PC852, // PC852
|
||||
};
|
||||
|
||||
} // namespace samsung_vfd
|
||||
} // namespace esphome
|
||||
@@ -1,16 +1,22 @@
|
||||
#include "samsung_vfd.h"
|
||||
#include "charmaps.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace samsung_vfd {
|
||||
|
||||
// Unaccented stand-ins for U+00C0..U+00FF (À..ÿ), one per code point.
|
||||
// Unaccented stand-ins for U+00C0..U+00FF (À..ÿ), one per code point. Used when
|
||||
// the selected code table has no glyph for a character.
|
||||
static const char LATIN1_FOLD[] = "AAAAAAACEEEEIIIIDNOOOOOxOUUUUYPs"
|
||||
"aaaaaaaceeeeiiiidnooooo/ouuuuypy";
|
||||
|
||||
static const char *const CODE_TABLE_NAMES[] = {"PC437", "PC850", "PC866", "PC737",
|
||||
"PC775", "CP10", "PC864", "PC852"};
|
||||
|
||||
void SamsungVfd::setup() {
|
||||
this->resize_lines_();
|
||||
this->init_display_();
|
||||
@@ -20,57 +26,168 @@ void SamsungVfd::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Samsung VFD:");
|
||||
ESP_LOGCONFIG(TAG, " Size: %u cols x %u rows", this->cols_, this->rows_);
|
||||
ESP_LOGCONFIG(TAG, " Newline between lines: %s", YESNO(this->send_newlines_));
|
||||
ESP_LOGCONFIG(TAG, " Brightness level: %u/4", this->brightness_level_);
|
||||
ESP_LOGCONFIG(TAG, " Code table: %s", CODE_TABLE_NAMES[this->code_table_ & 7]);
|
||||
ESP_LOGCONFIG(TAG, " International charset: %u", this->charset_);
|
||||
ESP_LOGCONFIG(TAG, " User characters: %u", (unsigned) this->user_chars_.size());
|
||||
}
|
||||
|
||||
void SamsungVfd::resize_lines_() {
|
||||
this->lines_.assign(this->rows_, std::string(this->cols_, ' '));
|
||||
}
|
||||
|
||||
void SamsungVfd::init_display_() {
|
||||
// ESC @ (reset), then clear.
|
||||
this->write_byte(CMD_ESC);
|
||||
this->write_byte(CMD_INIT);
|
||||
this->write_byte(CMD_CLEAR);
|
||||
// Force the next flush to retransmit whatever is in the shadow buffer.
|
||||
this->last_payload_.clear();
|
||||
}
|
||||
// ---------- low-level output ----------
|
||||
|
||||
void SamsungVfd::write_str_(const std::string &s) {
|
||||
if (!s.empty())
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(s.data()), s.size());
|
||||
}
|
||||
|
||||
std::string SamsungVfd::sanitize_(const std::string &text, bool keep_newlines) {
|
||||
void SamsungVfd::send_esc_(uint8_t cmd) {
|
||||
const uint8_t b[] = {CMD_ESC, cmd};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
void SamsungVfd::send_esc_(uint8_t cmd, uint8_t arg) {
|
||||
const uint8_t b[] = {CMD_ESC, cmd, arg};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
void SamsungVfd::send_us_(uint8_t cmd) {
|
||||
const uint8_t b[] = {CMD_US, cmd};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
void SamsungVfd::send_us_(uint8_t cmd, uint8_t arg) {
|
||||
const uint8_t b[] = {CMD_US, cmd, arg};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
// ---------- initialisation ----------
|
||||
|
||||
void SamsungVfd::init_display_() {
|
||||
// ESC @ resets every setting, clears the screen and homes the cursor.
|
||||
this->send_esc_(ESC_INIT);
|
||||
this->apply_settings_();
|
||||
// Force the next flush to retransmit whatever is in the shadow buffer.
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
void SamsungVfd::apply_settings_() {
|
||||
this->send_esc_(ESC_CODE_TABLE, this->code_table_);
|
||||
this->send_esc_(ESC_INTL_CHARSET, this->charset_);
|
||||
this->send_user_chars_();
|
||||
this->send_us_(US_BRIGHTNESS, this->brightness_level_);
|
||||
this->send_us_(US_BLINK, this->on_ ? this->blink_ : BLINK_BLANK);
|
||||
this->send_annunciators_();
|
||||
if (this->mode_ != MODE_OVERWRITE)
|
||||
this->send_us_(this->mode_);
|
||||
}
|
||||
|
||||
void SamsungVfd::send_user_chars_() {
|
||||
if (this->user_chars_.empty())
|
||||
return;
|
||||
for (const auto &uc : this->user_chars_) {
|
||||
// ESC & s n m a p1..p5: s = 1 byte per column, one character (n = m), 5 columns.
|
||||
const uint8_t b[] = {CMD_ESC, ESC_DEFINE_CHAR, 0x01, uc.code, uc.code, 0x05};
|
||||
this->write_array(b, sizeof(b));
|
||||
this->write_array(uc.columns.data(), uc.columns.size());
|
||||
}
|
||||
this->send_esc_(ESC_USER_FONT_SET, 0x01);
|
||||
}
|
||||
|
||||
void SamsungVfd::send_annunciators_() {
|
||||
const uint32_t all = 0x1FFFFE; // columns 1..20
|
||||
if ((this->annunciators_ & all) == all) {
|
||||
const uint8_t b[] = {CMD_US, US_ANNUNCIATOR, 0x01, 0x00};
|
||||
this->write_array(b, sizeof(b));
|
||||
return;
|
||||
}
|
||||
for (uint8_t col = 1; col <= 20; col++) {
|
||||
if (this->annunciators_ & (1UL << col)) {
|
||||
const uint8_t b[] = {CMD_US, US_ANNUNCIATOR, 0x01, col};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SamsungVfd::reinit() {
|
||||
this->init_display_();
|
||||
this->flush_();
|
||||
}
|
||||
|
||||
// ---------- text encoding ----------
|
||||
|
||||
char SamsungVfd::encode_codepoint_(uint32_t cp) const {
|
||||
if (this->code_table_ < sizeof(CHARMAPS) / sizeof(CHARMAPS[0])) {
|
||||
const uint16_t *map = CHARMAPS[this->code_table_];
|
||||
if (map != nullptr) {
|
||||
for (uint8_t i = 0; i < 128; i++) {
|
||||
if (map[i] == cp)
|
||||
return static_cast<char>(0x80 + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cp == 0xA0)
|
||||
return ' ';
|
||||
if (cp >= 0xC0 && cp <= 0xFF)
|
||||
return LATIN1_FOLD[cp - 0xC0];
|
||||
return '?';
|
||||
}
|
||||
|
||||
std::string SamsungVfd::encode(const std::string &text, bool keep_newlines) const {
|
||||
std::string out;
|
||||
out.reserve(text.size());
|
||||
for (size_t i = 0; i < text.size(); i++) {
|
||||
size_t i = 0;
|
||||
while (i < text.size()) {
|
||||
auto c = static_cast<uint8_t>(text[i]);
|
||||
if (c == '\n') {
|
||||
uint32_t cp;
|
||||
size_t len;
|
||||
if (c < 0x80) {
|
||||
cp = c;
|
||||
len = 1;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
cp = c & 0x1F;
|
||||
len = 2;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
cp = c & 0x0F;
|
||||
len = 3;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
cp = c & 0x07;
|
||||
len = 4;
|
||||
} else {
|
||||
i++; // stray continuation byte
|
||||
continue;
|
||||
}
|
||||
|
||||
bool valid = i + len <= text.size();
|
||||
for (size_t k = 1; valid && k < len; k++) {
|
||||
auto cc = static_cast<uint8_t>(text[i + k]);
|
||||
if ((cc & 0xC0) != 0x80)
|
||||
valid = false;
|
||||
else
|
||||
cp = (cp << 6) | (cc & 0x3F);
|
||||
}
|
||||
if (!valid) {
|
||||
out.push_back('?');
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
i += len;
|
||||
|
||||
if (cp == '\n') {
|
||||
out.push_back(keep_newlines ? '\n' : ' ');
|
||||
} else if (c < 0x20 || c == 0x7F) {
|
||||
} else if (cp < 0x20 || cp == 0x7F) {
|
||||
out.push_back(' ');
|
||||
} else if (c < 0x80) {
|
||||
out.push_back(static_cast<char>(c));
|
||||
} else if (c >= 0xC0) {
|
||||
// Start of a multi-byte UTF-8 sequence: decode 2-byte Latin-1 letters,
|
||||
// replace everything else with a single '?'.
|
||||
size_t len = c >= 0xF0 ? 4 : c >= 0xE0 ? 3 : 2;
|
||||
char repl = '?';
|
||||
if (len == 2 && i + 1 < text.size()) {
|
||||
uint32_t cp = ((c & 0x1F) << 6) | (static_cast<uint8_t>(text[i + 1]) & 0x3F);
|
||||
if (cp >= 0xC0 && cp <= 0xFF)
|
||||
repl = LATIN1_FOLD[cp - 0xC0];
|
||||
else if (cp == 0xA0)
|
||||
repl = ' ';
|
||||
}
|
||||
out.push_back(repl);
|
||||
i += len - 1;
|
||||
} else if (cp < 0x80) {
|
||||
out.push_back(static_cast<char>(cp));
|
||||
} else {
|
||||
out.push_back(this->encode_codepoint_(cp));
|
||||
}
|
||||
// Stray continuation bytes (0x80..0xBF) are dropped.
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------- buffered text ----------
|
||||
|
||||
void SamsungVfd::resize_lines_() { this->lines_.assign(this->rows_, std::string(this->cols_, ' ')); }
|
||||
|
||||
std::string SamsungVfd::fit_(const std::string &text, bool centered) const {
|
||||
std::string line = text.substr(0, this->cols_);
|
||||
if (centered && line.size() < this->cols_)
|
||||
@@ -81,7 +198,7 @@ std::string SamsungVfd::fit_(const std::string &text, bool centered) const {
|
||||
|
||||
void SamsungVfd::print(const std::string &raw, bool centered) {
|
||||
this->resize_lines_();
|
||||
const std::string text = sanitize_(raw, true);
|
||||
const std::string text = this->encode(raw, true);
|
||||
|
||||
// Lay the text out over the lines: split on explicit newlines, then wrap each
|
||||
// part into line-width chunks, breaking at the last space where possible.
|
||||
@@ -124,18 +241,16 @@ void SamsungVfd::print_line(uint8_t line, const std::string &text, bool centered
|
||||
if (this->lines_.size() < this->rows_)
|
||||
this->resize_lines_();
|
||||
|
||||
this->lines_[line] = this->fit_(sanitize_(text, false), centered);
|
||||
this->lines_[line] = this->fit_(this->encode(text, false), centered);
|
||||
this->flush_();
|
||||
}
|
||||
|
||||
void SamsungVfd::clear() {
|
||||
this->resize_lines_();
|
||||
this->last_payload_.clear();
|
||||
this->invalidate_();
|
||||
this->write_byte(CMD_CLEAR);
|
||||
}
|
||||
|
||||
void SamsungVfd::home() { this->write_byte(CMD_HOME); }
|
||||
|
||||
void SamsungVfd::flush_() {
|
||||
std::string payload;
|
||||
for (uint8_t r = 0; r < this->rows_; r++) {
|
||||
@@ -149,10 +264,161 @@ void SamsungVfd::flush_() {
|
||||
|
||||
// No clear: every line is padded to the full width, so going home and
|
||||
// overwriting replaces all characters without the blank-screen flicker.
|
||||
// In overwrite mode the cursor wraps from the end of line 1 to line 2.
|
||||
this->write_byte(CMD_HOME);
|
||||
this->write_str_(payload);
|
||||
this->last_payload_ = payload;
|
||||
}
|
||||
|
||||
// ---------- settings ----------
|
||||
|
||||
uint8_t SamsungVfd::brightness_to_level_(float b) {
|
||||
// Levels 1..4 = 40/60/80/100 %.
|
||||
int level = static_cast<int>(std::lround((b - 0.2f) / 0.2f));
|
||||
return static_cast<uint8_t>(std::max(1, std::min(4, level)));
|
||||
}
|
||||
|
||||
void SamsungVfd::set_brightness(float brightness) {
|
||||
this->brightness_level_ = brightness_to_level_(brightness);
|
||||
this->send_us_(US_BRIGHTNESS, this->brightness_level_);
|
||||
}
|
||||
|
||||
void SamsungVfd::set_blink(uint32_t ms) {
|
||||
// US E n: n * 13 ms on, n * 13 ms off. 0xFF means "off", so stop at 0xFE.
|
||||
this->blink_ = ms == 0 ? BLINK_OFF : static_cast<uint8_t>(std::max<uint32_t>(1, std::min<uint32_t>(0xFE, (ms + 6) / 13)));
|
||||
if (this->on_)
|
||||
this->send_us_(US_BLINK, this->blink_);
|
||||
}
|
||||
|
||||
void SamsungVfd::turn_off() {
|
||||
this->on_ = false;
|
||||
this->send_us_(US_BLINK, BLINK_BLANK);
|
||||
}
|
||||
|
||||
void SamsungVfd::turn_on() {
|
||||
this->on_ = true;
|
||||
this->send_us_(US_BLINK, this->blink_);
|
||||
}
|
||||
|
||||
void SamsungVfd::set_annunciator(uint8_t column, bool state) {
|
||||
if (column > 20) {
|
||||
ESP_LOGW(TAG, "set_annunciator: column %u out of range (0 = all, 1..20)", column);
|
||||
return;
|
||||
}
|
||||
if (column == 0) {
|
||||
this->annunciators_ = state ? 0x1FFFFE : 0;
|
||||
} else if (state) {
|
||||
this->annunciators_ |= 1UL << column;
|
||||
} else {
|
||||
this->annunciators_ &= ~(1UL << column);
|
||||
}
|
||||
const uint8_t b[] = {CMD_US, US_ANNUNCIATOR, static_cast<uint8_t>(state ? 1 : 0), column};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
void SamsungVfd::set_code_table(CodeTable table) {
|
||||
this->code_table_ = table;
|
||||
this->send_esc_(ESC_CODE_TABLE, table);
|
||||
}
|
||||
|
||||
void SamsungVfd::set_international_charset(InternationalCharset charset) {
|
||||
this->charset_ = charset;
|
||||
this->send_esc_(ESC_INTL_CHARSET, charset);
|
||||
}
|
||||
|
||||
void SamsungVfd::define_char(uint8_t code, const std::array<uint8_t, 5> &columns) {
|
||||
if (code < 0x20 || code > 0x7E) {
|
||||
ESP_LOGW(TAG, "define_char: code 0x%02X out of range (0x20..0x7E)", code);
|
||||
return;
|
||||
}
|
||||
auto it = std::find_if(this->user_chars_.begin(), this->user_chars_.end(),
|
||||
[code](const UserChar &uc) { return uc.code == code; });
|
||||
if (it != this->user_chars_.end()) {
|
||||
it->columns = columns;
|
||||
} else if (this->user_chars_.size() >= MAX_USER_CHARS) {
|
||||
ESP_LOGW(TAG, "define_char: at most %u user characters", MAX_USER_CHARS);
|
||||
return;
|
||||
} else {
|
||||
this->user_chars_.push_back(UserChar{code, columns});
|
||||
}
|
||||
const uint8_t b[] = {CMD_ESC, ESC_DEFINE_CHAR, 0x01, code, code, 0x05};
|
||||
this->write_array(b, sizeof(b));
|
||||
this->write_array(columns.data(), columns.size());
|
||||
this->send_esc_(ESC_USER_FONT_SET, 0x01);
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
void SamsungVfd::cancel_char(uint8_t code) {
|
||||
this->user_chars_.erase(std::remove_if(this->user_chars_.begin(), this->user_chars_.end(),
|
||||
[code](const UserChar &uc) { return uc.code == code; }),
|
||||
this->user_chars_.end());
|
||||
this->send_esc_(ESC_CANCEL_CHAR, code);
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
// ---------- raw cursor and mode commands ----------
|
||||
|
||||
void SamsungVfd::home() { this->write_byte(CMD_HOME); }
|
||||
void SamsungVfd::cursor_left() { this->write_byte(CMD_BS); }
|
||||
void SamsungVfd::cursor_right() { this->write_byte(CMD_HT); }
|
||||
void SamsungVfd::cursor_down() { this->write_byte(CMD_LF); }
|
||||
void SamsungVfd::cursor_up() { this->send_us_(US_CURSOR_UP); }
|
||||
void SamsungVfd::cursor_line_start() { this->write_byte(CMD_CR); }
|
||||
void SamsungVfd::cursor_line_end() { this->send_us_(US_LINE_END); }
|
||||
void SamsungVfd::cursor_bottom() { this->send_us_(US_BOTTOM); }
|
||||
|
||||
void SamsungVfd::set_cursor(uint8_t column, uint8_t line) {
|
||||
if (column >= this->cols_ || line >= this->rows_) {
|
||||
ESP_LOGW(TAG, "set_cursor: %u,%u out of range", column, line);
|
||||
return;
|
||||
}
|
||||
const uint8_t b[] = {CMD_US, US_SET_CURSOR, static_cast<uint8_t>(column + 1), static_cast<uint8_t>(line + 1)};
|
||||
this->write_array(b, sizeof(b));
|
||||
}
|
||||
|
||||
void SamsungVfd::clear_cursor_line() {
|
||||
this->write_byte(CMD_CAN);
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
void SamsungVfd::set_display_mode(DisplayMode mode) {
|
||||
this->mode_ = mode;
|
||||
this->send_us_(mode);
|
||||
if (mode != MODE_OVERWRITE)
|
||||
ESP_LOGW(TAG, "Scroll mode set: print/print_line assume overwrite mode and may scroll the screen");
|
||||
}
|
||||
|
||||
void SamsungVfd::select_peripheral(uint8_t n) {
|
||||
if (n < 1 || n > 3) {
|
||||
ESP_LOGW(TAG, "select_peripheral: %u out of range (1..3)", n);
|
||||
return;
|
||||
}
|
||||
this->send_esc_(ESC_PERIPHERAL, n);
|
||||
}
|
||||
|
||||
void SamsungVfd::self_test() {
|
||||
this->send_us_(US_SELF_TEST);
|
||||
this->invalidate_();
|
||||
// The self-test resets every setting except user characters and ignores
|
||||
// input for about 20 s; restore settings and the screen afterwards.
|
||||
this->set_timeout("self_test", 21000, [this]() {
|
||||
this->apply_settings_();
|
||||
this->flush_();
|
||||
});
|
||||
}
|
||||
|
||||
void SamsungVfd::write_text(const std::string &text) {
|
||||
this->write_str_(this->encode(text, false));
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
void SamsungVfd::write_raw(const std::vector<uint8_t> &data) { this->write_raw(data.data(), data.size()); }
|
||||
|
||||
void SamsungVfd::write_raw(const uint8_t *data, size_t len) {
|
||||
if (len != 0)
|
||||
this->write_array(data, len);
|
||||
this->invalidate_();
|
||||
}
|
||||
|
||||
} // namespace samsung_vfd
|
||||
} // namespace esphome
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -14,11 +15,78 @@ namespace samsung_vfd {
|
||||
|
||||
static const char *const TAG = "samsung_vfd";
|
||||
|
||||
// Control bytes, see section 5.4 of docs/20L203DA12-datasheet.pdf.
|
||||
// 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_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
|
||||
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<uint8_t, 5> columns; // one byte per column, bit 6 = top row, bit 0 = bottom row
|
||||
};
|
||||
|
||||
class SamsungVfd : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
@@ -27,44 +95,116 @@ class SamsungVfd : public Component, public uart::UARTDevice {
|
||||
// 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) { send_newlines_ = v; }
|
||||
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 cols_; }
|
||||
uint8_t rows() const { return rows_; }
|
||||
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();
|
||||
void home();
|
||||
|
||||
std::string line_text(uint8_t line) const {
|
||||
return line < lines_.size() ? lines_[line] : std::string();
|
||||
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<uint8_t, 5> &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<uint8_t> &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 single-byte) into one line: truncate or pad, optionally centred.
|
||||
/// 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);
|
||||
|
||||
/// 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);
|
||||
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<std::string> 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<UserChar> user_chars_;
|
||||
};
|
||||
|
||||
// ---------- actions ----------
|
||||
@@ -120,5 +260,81 @@ template<typename... Ts> class HomeAction : public Action<Ts...>, public Parente
|
||||
void play(const Ts &...x) override { this->parent_->home(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetBrightnessAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, brightness)
|
||||
void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetBlinkAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint32_t, interval)
|
||||
void play(const Ts &...x) override { this->parent_->set_blink(this->interval_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class TurnOnAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->turn_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class TurnOffAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->turn_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetAnnunciatorAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(int, column)
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
void play(const Ts &...x) override {
|
||||
this->parent_->set_annunciator(static_cast<uint8_t>(this->column_.value(x...)), this->state_.value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCursorAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(int, column)
|
||||
TEMPLATABLE_VALUE(int, line)
|
||||
void play(const Ts &...x) override {
|
||||
this->parent_->set_cursor(static_cast<uint8_t>(this->column_.value(x...)),
|
||||
static_cast<uint8_t>(this->line_.value(x...)));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDisplayModeAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
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<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, text)
|
||||
void set_data(const std::vector<uint8_t> &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<uint8_t> data_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReinitAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->reinit(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SelfTestAction : public Action<Ts...>, public Parented<SamsungVfd> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->self_test(); }
|
||||
};
|
||||
|
||||
} // namespace samsung_vfd
|
||||
} // namespace esphome
|
||||
Reference in new issue
Block a user