1
0
mirror of https://github.com/esphome/esphome.git synced 2026-03-18 17:26:47 +01:00

Nextion upload and sensors (#1464)

Co-authored-by: Senex Crenshaw <senexcrenshaw@gmail.com>
This commit is contained in:
SenexCrenshaw 2021-07-14 20:51:15 -04:00 committed by GitHub
parent 0992609bf4
commit 0651716b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3295 additions and 263 deletions

View File

@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov
esphome/components/midea_dongle/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/network/* @esphome/core
esphome/components/nextion/* @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw
esphome/components/nextion/sensor/* @senexcrenshaw
esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz
esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core

View File

@ -1,3 +1,8 @@
import esphome.codegen as cg
from esphome.components import uart
nextion_ns = cg.esphome_ns.namespace("nextion")
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
nextion_ref = Nextion.operator("ref")
CONF_NEXTION_ID = "nextion_id"

View File

@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/automation.h"
#include "nextion.h"
namespace esphome {
namespace nextion {
class SetupTrigger : public Trigger<> {
public:
explicit SetupTrigger(Nextion *nextion) {
nextion->add_setup_state_callback([this]() { this->trigger(); });
}
};
class SleepTrigger : public Trigger<> {
public:
explicit SleepTrigger(Nextion *nextion) {
nextion->add_sleep_state_callback([this]() { this->trigger(); });
}
};
class WakeTrigger : public Trigger<> {
public:
explicit WakeTrigger(Nextion *nextion) {
nextion->add_wake_state_callback([this]() { this->trigger(); });
}
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,126 @@
from string import ascii_letters, digits
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import color
from . import CONF_NEXTION_ID
from . import Nextion
CONF_VARIABLE_NAME = "variable_name"
CONF_COMPONENT_NAME = "component_name"
CONF_WAVE_CHANNEL_ID = "wave_channel_id"
CONF_WAVE_MAX_VALUE = "wave_max_value"
CONF_PRECISION = "precision"
CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value"
CONF_TFT_URL = "tft_url"
CONF_ON_SLEEP = "on_sleep"
CONF_ON_WAKE = "on_wake"
CONF_ON_SETUP = "on_setup"
CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout"
CONF_WAKE_UP_PAGE = "wake_up_page"
CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch"
CONF_WAVE_MAX_LENGTH = "wave_max_length"
CONF_BACKGROUND_COLOR = "background_color"
CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color"
CONF_FOREGROUND_COLOR = "foreground_color"
CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color"
CONF_FONT_ID = "font_id"
CONF_VISIBLE = "visible"
def NextionName(value):
valid_chars = ascii_letters + digits + "."
if not isinstance(value, str) or len(value) > 29:
raise cv.Invalid("Must be a string less than 29 characters")
for char in value:
if char not in valid_chars:
raise cv.Invalid(
"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format(
char
)
)
return value
CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color),
cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color),
cv.Optional(CONF_VISIBLE, default=True): cv.boolean,
}
)
CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
cv.Schema(
{
cv.Required(CONF_COMPONENT_NAME): NextionName,
cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
}
)
)
CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
cv.Schema(
{
cv.Optional(CONF_COMPONENT_NAME): NextionName,
cv.Optional(CONF_VARIABLE_NAME): NextionName,
}
)
)
CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend(
cv.Schema(
{
cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
}
)
)
CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend(
cv.Schema(
{
cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color),
cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color),
}
)
)
async def setup_component_core_(var, config, arg):
if CONF_VARIABLE_NAME in config:
cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME]))
elif CONF_COMPONENT_NAME in config:
cg.add(
var.set_variable_name(
config[CONF_COMPONENT_NAME],
config[CONF_COMPONENT_NAME] + arg,
)
)
if CONF_BACKGROUND_COLOR in config:
color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR])
cg.add(var.set_background_color(color_component))
if CONF_BACKGROUND_PRESSED_COLOR in config:
color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR])
cg.add(var.set_background_pressed_color(color_component))
if CONF_FOREGROUND_COLOR in config:
color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR])
cg.add(var.set_foreground_color(color_component))
if CONF_FOREGROUND_PRESSED_COLOR in config:
color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR])
cg.add(var.set_foreground_pressed_color(color_component))
if CONF_FONT_ID in config:
cg.add(var.set_font_id(config[CONF_FONT_ID]))
if CONF_VISIBLE in config:
cg.add(var.set_visible(config[CONF_VISIBLE]))

View File

@ -1,34 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
from . import nextion_ns
from .display import Nextion
DEPENDENCIES = ["display"]
CONF_NEXTION_ID = "nextion_id"
NextionTouchComponent = nextion_ns.class_(
"NextionTouchComponent", binary_sensor.BinarySensor
)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(NextionTouchComponent),
cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
cv.Required(CONF_PAGE_ID): cv.uint8_t,
cv.Required(CONF_COMPONENT_ID): cv.uint8_t,
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await binary_sensor.register_binary_sensor(var, config)
hub = await cg.get_variable(config[CONF_NEXTION_ID])
cg.add(hub.register_touch_component(var))
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
cg.add(var.set_page_id(config[CONF_PAGE_ID]))

View File

@ -0,0 +1,54 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
from .. import nextion_ns, CONF_NEXTION_ID
from ..base_component import (
setup_component_core_,
CONFIG_BINARY_SENSOR_SCHEMA,
CONF_VARIABLE_NAME,
CONF_COMPONENT_NAME,
)
CODEOWNERS = ["@senexcrenshaw"]
NextionBinarySensor = nextion_ns.class_(
"NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
)
CONFIG_SCHEMA = cv.All(
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(NextionBinarySensor),
cv.Optional(CONF_PAGE_ID): cv.uint8_t,
cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
}
)
.extend(CONFIG_BINARY_SENSOR_SCHEMA)
.extend(cv.polling_component_schema("never")),
cv.has_at_least_one_key(
CONF_PAGE_ID,
CONF_COMPONENT_ID,
CONF_COMPONENT_NAME,
CONF_VARIABLE_NAME,
),
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_NEXTION_ID])
var = cg.new_Pvariable(config[CONF_ID], hub)
await binary_sensor.register_binary_sensor(var, config)
await cg.register_component(var, config)
if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}:
cg.add(hub.register_touch_component(var))
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
cg.add(var.set_page_id(config[CONF_PAGE_ID]))
if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config:
await setup_component_core_(var, config, ".val")
cg.add(hub.register_binarysensor_component(var))

View File

@ -0,0 +1,69 @@
#include "nextion_binarysensor.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_binarysensor";
void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) {
if (!this->nextion_->is_setup())
return;
if (this->variable_name_.empty()) // This is a touch component
return;
if (this->variable_name_ == variable_name) {
this->publish_state(state);
ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
}
}
void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) {
if (this->page_id_ == page_id && this->component_id_ == component_id) {
this->publish_state(state);
}
}
void NextionBinarySensor::update() {
if (!this->nextion_->is_setup())
return;
if (this->variable_name_.empty()) // This is a touch component
return;
this->nextion_->add_to_get_queue(this);
}
void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) {
if (!this->nextion_->is_setup())
return;
if (this->component_id_ == 0) // This is a legacy touch component
return;
if (send_to_nextion) {
if (this->nextion_->is_sleeping() || !this->visible_) {
this->needs_to_send_update_ = true;
} else {
this->needs_to_send_update_ = false;
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
}
}
if (publish) {
this->publish_state(state);
} else {
this->state = state;
this->has_state_ = true;
}
this->update_component_settings();
ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(),
ONOFF(this->variable_name_.c_str()));
}
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,42 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "../nextion_component.h"
#include "../nextion_base.h"
namespace esphome {
namespace nextion {
class NextionBinarySensor;
class NextionBinarySensor : public NextionComponent,
public binary_sensor::BinarySensorInitiallyOff,
public PollingComponent {
public:
NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; }
void update_component() override { this->update(); }
void update() override;
void send_state_to_nextion() override { this->set_state(this->state, false); };
void process_bool(const std::string &variable_name, bool state) override;
void process_touch(uint8_t page_id, uint8_t component_id, bool state) override;
// Set the components page id for Nextion Touch Component
void set_page_id(uint8_t page_id) { page_id_ = page_id; }
// Set the components component id for Nextion Touch Component
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
void set_state(bool state) override { this->set_state(state, true, true); }
void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
void set_state(bool state, bool publish, bool send_to_nextion) override;
NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; }
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
this->set_state(state_value != 0, publish, send_to_nextion);
}
protected:
uint8_t page_id_;
};
} // namespace nextion
} // namespace esphome

View File

@ -1,20 +1,58 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import display, uart
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS
from . import nextion_ns
from esphome.const import (
CONF_ID,
CONF_LAMBDA,
CONF_BRIGHTNESS,
CONF_TRIGGER_ID,
)
from . import Nextion, nextion_ns, nextion_ref
from .base_component import (
CONF_ON_SLEEP,
CONF_ON_WAKE,
CONF_ON_SETUP,
CONF_TFT_URL,
CONF_TOUCH_SLEEP_TIMEOUT,
CONF_WAKE_UP_PAGE,
CONF_AUTO_WAKE_ON_TOUCH,
)
CODEOWNERS = ["@senexcrenshaw"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["binary_sensor"]
AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"]
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
NextionRef = Nextion.operator("ref")
SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template())
SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template())
WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template())
CONFIG_SCHEMA = (
display.BASIC_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Nextion),
cv.Optional(CONF_TFT_URL): cv.string,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_ON_SETUP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger),
}
),
cv.Optional(CONF_ON_SLEEP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger),
}
),
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger),
}
),
cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535),
cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int,
cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean,
}
)
.extend(cv.polling_component_schema("5s"))
@ -31,8 +69,33 @@ async def to_code(config):
cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void
config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
if CONF_TFT_URL in config:
cg.add_define("USE_TFT_UPLOAD")
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
if CONF_TOUCH_SLEEP_TIMEOUT in config:
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
if CONF_WAKE_UP_PAGE in config:
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
if CONF_AUTO_WAKE_ON_TOUCH in config:
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
await display.register_display(var, config)
for conf in config.get(CONF_ON_SETUP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_SLEEP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)

View File

@ -1,5 +1,7 @@
#include "nextion.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace nextion {
@ -7,69 +9,171 @@ namespace nextion {
static const char *const TAG = "nextion";
void Nextion::setup() {
this->send_command_no_ack("");
this->send_command_printf("bkcmd=3");
this->set_backlight_brightness(static_cast<uint8_t>(brightness_ * 100));
this->goto_page("0");
this->is_setup_ = false;
this->ignore_is_setup_ = true;
// Wake up the nextion
this->send_command_("bkcmd=0");
this->send_command_("sleep=0");
this->send_command_("bkcmd=0");
this->send_command_("sleep=0");
// Reboot it
this->send_command_("rest");
this->ignore_is_setup_ = false;
}
float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; }
bool Nextion::send_command_(const std::string &command) {
if (!this->ignore_is_setup_ && !this->is_setup()) {
return false;
}
ESP_LOGN(TAG, "send_command %s", command.c_str());
this->write_str(command.c_str());
const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
this->write_array(to_send, sizeof(to_send));
return true;
}
bool Nextion::check_connect_() {
if (this->get_is_connected_())
return true;
if (this->comok_sent_ == 0) {
this->reset_(false);
this->ignore_is_setup_ = true;
this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating
this->send_command_("connect");
this->comok_sent_ = millis();
this->ignore_is_setup_ = false;
return false;
}
if (millis() - this->comok_sent_ <= 500) // Wait 500 ms
return false;
std::string response;
this->recv_ret_string_(response, 0, false);
if (response.empty() || response.find("comok") == std::string::npos) {
#ifdef NEXTION_PROTOCOL_LOG
ESP_LOGN(TAG, "Bad connect request %s", response.c_str());
for (int i = 0; i < response.length(); i++) {
ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]);
}
#endif
ESP_LOGW(TAG, "Nextion is not connected! ");
comok_sent_ = 0;
return false;
}
this->ignore_is_setup_ = true;
ESP_LOGI(TAG, "Nextion is connected");
this->is_connected_ = true;
ESP_LOGN(TAG, "connect request %s", response.c_str());
size_t start;
size_t end = 0;
std::vector<std::string> connect_info;
while ((start = response.find_first_not_of(',', end)) != std::string::npos) {
end = response.find(',', start);
connect_info.push_back(response.substr(start, end - start));
}
if (connect_info.size() == 7) {
ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size());
this->device_model_ = connect_info[2];
this->firmware_version_ = connect_info[3];
this->serial_number_ = connect_info[5];
this->flash_size_ = connect_info[6];
} else {
ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str());
}
this->ignore_is_setup_ = false;
this->dump_config();
return true;
}
void Nextion::reset_(bool reset_nextion) {
uint8_t d;
while (this->available()) { // Clear receive buffer
this->read_byte(&d);
};
this->nextion_queue_.clear();
}
void Nextion::dump_config() {
ESP_LOGCONFIG(TAG, "Nextion:");
ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str());
ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str());
ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str());
ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str());
ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False");
if (this->touch_sleep_timeout_ != 0) {
ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_);
}
if (this->wake_up_page_ != -1) {
ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_);
}
}
float Nextion::get_setup_priority() const { return setup_priority::DATA; }
void Nextion::update() {
if (!this->is_setup()) {
return;
}
if (this->writer_.has_value()) {
(*this->writer_)(*this);
}
}
void Nextion::send_command_no_ack(const char *command) {
// Flush RX...
this->loop();
this->write_str(command);
const uint8_t data[3] = {0xFF, 0xFF, 0xFF};
this->write_array(data, sizeof(data));
void Nextion::add_sleep_state_callback(std::function<void()> &&callback) {
this->sleep_callback_.add(std::move(callback));
}
bool Nextion::ack_() {
if (!this->wait_for_ack_)
return true;
void Nextion::add_wake_state_callback(std::function<void()> &&callback) {
this->wake_callback_.add(std::move(callback));
}
uint32_t start = millis();
while (!this->read_until_ack_()) {
if (millis() - start > 100) {
ESP_LOGW(TAG, "Waiting for ACK timed out!");
return false;
}
void Nextion::add_setup_state_callback(std::function<void()> &&callback) {
this->setup_callback_.add(std::move(callback));
}
void Nextion::update_all_components() {
if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
return;
for (auto *binarysensortype : this->binarysensortype_) {
binarysensortype->update_component();
}
for (auto *sensortype : this->sensortype_) {
sensortype->update_component();
}
for (auto *switchtype : this->switchtype_) {
switchtype->update_component();
}
for (auto *textsensortype : this->textsensortype_) {
textsensortype->update_component();
}
return true;
}
void Nextion::set_component_text(const char *component, const char *text) {
this->send_command_printf("%s.txt=\"%s\"", component, text);
}
void Nextion::set_component_value(const char *component, int value) {
this->send_command_printf("%s.val=%d", component, value);
}
void Nextion::display_picture(int picture_id, int x_start, int y_start) {
this->send_command_printf("pic %d %d %d", x_start, y_start, picture_id);
}
void Nextion::set_component_background_color(const char *component, const char *color) {
this->send_command_printf("%s.bco=\"%s\"", component, color);
}
void Nextion::set_component_pressed_background_color(const char *component, const char *color) {
this->send_command_printf("%s.bco2=\"%s\"", component, color);
}
void Nextion::set_component_font_color(const char *component, const char *color) {
this->send_command_printf("%s.pco=\"%s\"", component, color);
}
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {
this->send_command_printf("%s.pco2=\"%s\"", component, color);
}
void Nextion::set_component_coordinates(const char *component, int x, int y) {
this->send_command_printf("%s.xcen=%d", component, x);
this->send_command_printf("%s.ycen=%d", component, y);
}
void Nextion::set_component_font(const char *component, uint8_t font_id) {
this->send_command_printf("%s.font=%d", component, font_id);
}
void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); }
bool Nextion::send_command_printf(const char *format, ...) {
if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
return false;
char buffer[256];
va_list arg;
va_start(arg, format);
@ -79,208 +183,911 @@ bool Nextion::send_command_printf(const char *format, ...) {
ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
return false;
}
this->send_command_no_ack(buffer);
if (!this->ack_()) {
ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer);
if (this->send_command_(buffer)) {
this->add_no_result_to_queue_("send_command_printf");
return true;
}
return false;
}
#ifdef NEXTION_PROTOCOL_LOG
void Nextion::print_queue_members_() {
ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size());
ESP_LOGN(TAG, "*******************************************");
int count = 0;
for (auto *i : this->nextion_queue_) {
if (count++ == 10)
break;
if (i == nullptr) {
ESP_LOGN(TAG, "Nextion queue is null");
} else {
ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(),
i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str());
}
}
ESP_LOGN(TAG, "*******************************************");
}
#endif
void Nextion::loop() {
if (!this->check_connect_() || this->is_updating_)
return;
if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) {
this->ignore_is_setup_ = true;
this->sent_setup_commands_ = true;
this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command.
this->set_backlight_brightness(this->brightness_);
this->goto_page("0");
this->set_auto_wake_on_touch(this->auto_wake_on_touch_);
if (this->touch_sleep_timeout_ != 0) {
this->set_touch_sleep_timeout(this->touch_sleep_timeout_);
}
if (this->wake_up_page_ != -1) {
this->set_wake_up_page(this->wake_up_page_);
}
this->ignore_is_setup_ = false;
}
this->process_serial_(); // Receive serial data
this->process_nextion_commands_(); // Process nextion return commands
if (!this->nextion_reports_is_setup_) {
if (this->started_ms_ == 0)
this->started_ms_ = millis();
if (this->started_ms_ + this->startup_override_ms_ < millis()) {
ESP_LOGD(TAG, "Manually set nextion report ready");
this->nextion_reports_is_setup_ = true;
}
}
}
bool Nextion::remove_from_q_(bool report_empty) {
if (this->nextion_queue_.empty()) {
if (report_empty)
ESP_LOGE(TAG, "Nextion queue is empty!");
return false;
}
NextionQueue *nb = this->nextion_queue_.front();
NextionComponentBase *component = nb->component;
ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str());
if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
delete component;
}
delete nb;
this->nextion_queue_.pop_front();
return true;
}
void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); }
void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); }
void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); }
void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); }
void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) {
this->send_command_printf("add %d,%u,%u", component_id, channel_number, value);
}
void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) {
this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color);
}
void Nextion::line(int x1, int y1, int x2, int y2, const char *color) {
this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color);
}
void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) {
this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color);
}
void Nextion::circle(int center_x, int center_y, int radius, const char *color) {
this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color);
}
void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) {
this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color);
}
bool Nextion::read_until_ack_() {
while (this->available() >= 4) {
// flush preceding filler bytes
uint8_t temp;
while (this->available() && this->peek_byte(&temp) && temp == 0xFF)
this->read_byte(&temp);
if (!this->available())
break;
void Nextion::process_serial_() {
uint8_t d;
uint8_t event;
// event type
this->read_byte(&event);
while (this->available()) {
read_byte(&d);
this->command_data_ += d;
}
}
// nextion.tech/instruction-set/
void Nextion::process_nextion_commands_() {
if (this->command_data_.length() == 0) {
return;
}
uint8_t data[255];
// total length of data (including end bytes)
uint8_t data_length = 0;
// message is terminated by three consecutive 0xFF
// this variable keeps track of ohow many of those have
// been received
uint8_t end_length = 0;
while (this->available() && end_length < 3 && data_length < sizeof(data)) {
uint8_t byte;
this->read_byte(&byte);
if (byte == 0xFF) {
end_length++;
} else {
end_length = 0;
}
data[data_length++] = byte;
size_t to_process_length = 0;
std::string to_process;
ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length());
#ifdef NEXTION_PROTOCOL_LOG
this->print_queue_members_();
#endif
while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) {
ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size());
while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() &&
static_cast<uint8_t>(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) {
++to_process_length;
ESP_LOGN(TAG, "Add extra 0xFF to process");
}
if (end_length != 3) {
ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!");
continue;
}
this->nextion_event_ = this->command_data_[0];
data_length -= 3; // remove filler bytes
to_process_length -= 1;
to_process = this->command_data_.substr(1, to_process_length);
bool invalid_data_length = false;
switch (event) {
case 0x01: // successful execution of instruction (ACK)
return true;
case 0x00: // invalid instruction
switch (this->nextion_event_) {
case 0x00: // instruction sent by user has failed
ESP_LOGW(TAG, "Nextion reported invalid instruction!");
this->remove_from_q_();
break;
case 0x02: // component ID invalid
ESP_LOGW(TAG, "Nextion reported component ID invalid!");
case 0x01: // instruction sent by user was successful
ESP_LOGVV(TAG, "instruction sent by user was successful");
ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False");
this->remove_from_q_();
if (!this->is_setup_) {
if (this->nextion_queue_.empty()) {
ESP_LOGD(TAG, "Nextion is setup");
this->is_setup_ = true;
this->setup_callback_.call();
}
}
break;
case 0x03: // page ID invalid
case 0x02: // invalid Component ID or name was used
this->remove_from_q_();
break;
case 0x03: // invalid Page ID or name was used
ESP_LOGW(TAG, "Nextion reported page ID invalid!");
this->remove_from_q_();
break;
case 0x04: // picture ID invalid
case 0x04: // invalid Picture ID was used
ESP_LOGW(TAG, "Nextion reported picture ID invalid!");
this->remove_from_q_();
break;
case 0x05: // font ID invalid
case 0x05: // invalid Font ID was used
ESP_LOGW(TAG, "Nextion reported font ID invalid!");
this->remove_from_q_();
break;
case 0x11: // baud rate setting invalid
case 0x06: // File operation fails
ESP_LOGW(TAG, "Nextion File operation fail!");
break;
case 0x09: // Instructions with CRC validation fails their CRC check
ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!");
break;
case 0x11: // invalid Baud rate was used
ESP_LOGW(TAG, "Nextion reported baud rate invalid!");
break;
case 0x12: // curve control ID number or channel number is invalid
ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!");
case 0x12: // invalid Waveform ID or Channel # was used
if (!this->nextion_queue_.empty()) {
int index = 0;
int found = -1;
for (auto &nb : this->nextion_queue_) {
NextionComponentBase *component = nb->component;
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!",
component->get_component_id(), component->get_wave_channel_id());
ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d",
component->get_component_id(), component->get_wave_channel_id());
found = index;
delete component;
delete nb;
break;
}
++index;
}
if (found != -1) {
this->nextion_queue_.erase(this->nextion_queue_.begin() + found);
} else {
ESP_LOGW(
TAG,
"Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!");
}
}
break;
case 0x1A: // variable name invalid
ESP_LOGW(TAG, "Nextion reported variable name invalid!");
this->remove_from_q_();
break;
case 0x1B: // variable operation invalid
ESP_LOGW(TAG, "Nextion reported variable operation invalid!");
this->remove_from_q_();
break;
case 0x1C: // failed to assign
ESP_LOGW(TAG, "Nextion reported failed to assign variable!");
this->remove_from_q_();
break;
case 0x1D: // operate EEPROM failed
ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!");
break;
case 0x1E: // parameter quantity invalid
ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!");
this->remove_from_q_();
break;
case 0x1F: // IO operation failed
ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!");
break;
case 0x20: // undefined escape characters
ESP_LOGW(TAG, "Nextion reported undefined escape characters!");
this->remove_from_q_();
break;
case 0x23: // too long variable name
ESP_LOGW(TAG, "Nextion reported too long variable name!");
this->remove_from_q_();
break;
case 0x24: // Serial Buffer overflow occurs
ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!");
break;
case 0x65: { // touch event return data
if (data_length != 3) {
invalid_data_length = true;
if (to_process_length != 3) {
ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length);
break;
}
uint8_t page_id = data[0];
uint8_t component_id = data[1];
uint8_t touch_event = data[2]; // 0 -> release, 1 -> press
uint8_t page_id = to_process[0];
uint8_t component_id = to_process[1];
uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press
ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id,
touch_event ? "PRESS" : "RELEASE");
for (auto *touch : this->touch_) {
touch->process(page_id, component_id, touch_event);
touch->process_touch(page_id, component_id, touch_event != 0);
}
break;
}
case 0x67:
case 0x68: { // touch coordinate data
if (data_length != 5) {
invalid_data_length = true;
case 0x67: { // Touch Coordinate (awake)
break;
}
case 0x68: { // touch coordinate data (sleep)
if (to_process_length != 5) {
ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length);
ESP_LOGW(TAG, "%s", to_process.c_str());
break;
}
uint16_t x = (uint16_t(data[0]) << 8) | data[1];
uint16_t y = (uint16_t(data[2]) << 8) | data[3];
uint8_t touch_event = data[4]; // 0 -> release, 1 -> press
uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE");
break;
}
case 0x66: // sendme page id
case 0x66: {
break;
} // sendme page id
// 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF
// Returned when using get command for a string.
// Each byte is converted to char.
// data: ab123
case 0x70: // string variable data return
{
if (this->nextion_queue_.empty()) {
ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty");
break;
}
NextionQueue *nb = this->nextion_queue_.front();
NextionComponentBase *component = nb->component;
if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) {
ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor",
component->get_variable_name().c_str());
} else {
ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(),
component->get_variable_name().c_str(), component->get_queue_type_string().c_str());
component->set_state_from_string(to_process, true, false);
}
delete nb;
this->nextion_queue_.pop_front();
break;
}
// 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF
// Returned when get command to return a number
// 4 byte 32-bit value in little endian order.
// (0x01+0x02*256+0x03*65536+0x04*16777216)
// data: 67305985
case 0x71: // numeric variable data return
case 0x86: // device automatically enters into sleep mode
{
if (this->nextion_queue_.empty()) {
ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty");
break;
}
if (to_process_length == 0) {
ESP_LOGE(TAG, "ERROR: Received numeric return but no data!");
break;
}
int dataindex = 0;
int value = 0;
for (int i = 0; i < 4; ++i) {
value += to_process[i] << (8 * i);
++dataindex;
}
NextionQueue *nb = this->nextion_queue_.front();
NextionComponentBase *component = nb->component;
if (component->get_queue_type() != NextionQueueType::SENSOR &&
component->get_queue_type() != NextionQueueType::BINARY_SENSOR &&
component->get_queue_type() != NextionQueueType::SWITCH) {
ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d",
component->get_variable_name().c_str(), component->get_queue_type());
} else {
ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d",
component->get_variable_name().c_str(), component->get_queue_type(),
component->get_queue_type_string().c_str(), value);
component->set_state_from_int(value, true, false);
}
delete nb;
this->nextion_queue_.pop_front();
break;
}
case 0x86: { // device automatically enters into sleep mode
ESP_LOGVV(TAG, "Received Nextion entering sleep automatically");
this->is_sleeping_ = true;
this->sleep_callback_.call();
break;
}
case 0x87: // device automatically wakes up
{
ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically");
this->is_sleeping_ = false;
this->wake_callback_.call();
break;
}
case 0x88: // system successful start up
case 0x89: // start SD card upgrade
case 0xFD: // data transparent transmit finished
case 0xFE: // data transparent transmit ready
{
ESP_LOGD(TAG, "system successful start up %zu", to_process_length);
this->nextion_reports_is_setup_ = true;
break;
}
case 0x89: { // start SD card upgrade
break;
}
// Data from nextion is
// 0x90 - Start
// variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
// 00 - NULL
// 00/01 - Single byte for on/off
// FF FF FF - End
case 0x90: { // Switched component
std::string variable_name;
uint8_t index = 0;
// Get variable name
index = to_process.find('\0');
if (static_cast<char>(index) == std::string::npos || (to_process_length - index - 1) < 1) {
ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!");
ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
break;
}
variable_name = to_process.substr(0, index);
++index;
ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0);
for (auto *switchtype : this->switchtype_) {
switchtype->process_bool(variable_name, to_process[index] != 0);
}
break;
}
// Data from nextion is
// 0x91 - Start
// variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
// 00 - NULL
// variable length of 0x71 return data: prints temp1.val,0
// FF FF FF - End
case 0x91: { // Sensor component
std::string variable_name;
uint8_t index = 0;
index = to_process.find('\0');
if (static_cast<char>(index) == std::string::npos || (to_process_length - index - 1) != 4) {
ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!");
ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
break;
}
index = to_process.find('\0');
variable_name = to_process.substr(0, index);
// // Get variable name
int value = 0;
for (int i = 0; i < 4; ++i) {
value += to_process[i + index + 1] << (8 * i);
}
ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value);
for (auto *sensor : this->sensortype_) {
sensor->process_sensor(variable_name, value);
}
break;
}
// Data from nextion is
// 0x92 - Start
// variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
// 00 - NULL
// variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0
// 00 - NULL
// FF FF FF - End
case 0x92: { // Text Sensor Component
std::string variable_name;
std::string text_value;
uint8_t index = 0;
// Get variable name
index = to_process.find('\0');
if (static_cast<char>(index) == std::string::npos || (to_process_length - index - 1) < 1) {
ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!");
ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
break;
}
variable_name = to_process.substr(0, index);
++index;
text_value = to_process.substr(index);
ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str());
// NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue;
// nq->variable_name = variable_name;
// nq->state = text_value;
// this->textsensorq_.push_back(nq);
for (auto *textsensortype : this->textsensortype_) {
textsensortype->process_text(variable_name, text_value);
}
break;
}
// Data from nextion is
// 0x93 - Start
// variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
// 00 - NULL
// 00/01 - Single byte for on/off
// FF FF FF - End
case 0x93: { // Binary Sensor component
std::string variable_name;
uint8_t index = 0;
// Get variable name
index = to_process.find('\0');
if (static_cast<char>(index) == std::string::npos || (to_process_length - index - 1) < 1) {
ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!");
ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
break;
}
variable_name = to_process.substr(0, index);
++index;
ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0);
for (auto *binarysensortype : this->binarysensortype_) {
binarysensortype->process_bool(&variable_name[0], to_process[index] != 0);
}
break;
}
case 0xFD: { // data transparent transmit finished
ESP_LOGVV(TAG, "Nextion reported data transmit finished!");
break;
}
case 0xFE: { // data transparent transmit ready
ESP_LOGVV(TAG, "Nextion reported ready for transmit!");
int index = 0;
int found = -1;
for (auto &nb : this->nextion_queue_) {
auto component = nb->component;
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size()
: 255; // ADDT command can only send 255
this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu",
component->get_component_id(), component->get_wave_channel_id(), buffer_to_send);
if (component->get_wave_buffer().size() <= 255) {
component->get_wave_buffer().clear();
} else {
component->get_wave_buffer().erase(component->get_wave_buffer().begin(),
component->get_wave_buffer().begin() + buffer_to_send);
}
found = index;
delete component;
delete nb;
break;
}
++index;
}
if (found == -1) {
ESP_LOGE(TAG, "No waveforms in queue to send data!");
break;
} else {
this->nextion_queue_.erase(this->nextion_queue_.begin() + found);
}
break;
}
default:
ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event);
ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_);
break;
}
if (invalid_data_length) {
ESP_LOGW(TAG, "Invalid data length from nextion!");
// ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1);
this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
// App.feed_wdt(); Remove before master merge
this->process_serial_();
}
uint32_t ms = millis();
if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
for (int i = 0; i < this->nextion_queue_.size(); i++) {
NextionComponentBase *component = this->nextion_queue_[i]->component;
if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) {
if (this->nextion_queue_[i]->queue_time == 0)
ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0",
component->get_queue_type_string().c_str(), component->get_variable_name().c_str());
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
delete component;
}
delete this->nextion_queue_[i];
this->nextion_queue_.erase(this->nextion_queue_.begin() + i);
} else {
break;
}
}
}
ESP_LOGN(TAG, "Loop End");
// App.feed_wdt(); Remove before master merge
this->process_serial_();
} // namespace nextion
void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) {
this->set_nextion_sensor_state(static_cast<NextionQueueType>(queue_type), name, state);
}
void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) {
ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type);
switch (queue_type) {
case NextionQueueType::SENSOR: {
for (auto *sensor : this->sensortype_) {
if (name == sensor->get_variable_name()) {
sensor->set_state(state, true, true);
break;
}
}
break;
}
case NextionQueueType::BINARY_SENSOR: {
for (auto *sensor : this->binarysensortype_) {
if (name == sensor->get_variable_name()) {
sensor->set_state(state != 0, true, true);
break;
}
}
break;
}
case NextionQueueType::SWITCH: {
for (auto *sensor : this->switchtype_) {
if (name == sensor->get_variable_name()) {
sensor->set_state(state != 0, true, true);
break;
}
}
break;
}
default: {
ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type);
}
}
}
void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) {
ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str());
for (auto *sensor : this->textsensortype_) {
if (name == sensor->get_variable_name()) {
sensor->set_state(state, true, true);
break;
}
}
}
void Nextion::all_components_send_state_(bool force_update) {
ESP_LOGD(TAG, "all_components_send_state_ ");
for (auto *binarysensortype : this->binarysensortype_) {
if (force_update || binarysensortype->get_needs_to_send_update())
binarysensortype->send_state_to_nextion();
}
for (auto *sensortype : this->sensortype_) {
if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0)
sensortype->send_state_to_nextion();
}
for (auto *switchtype : this->switchtype_) {
if (force_update || switchtype->get_needs_to_send_update())
switchtype->send_state_to_nextion();
}
for (auto *textsensortype : this->textsensortype_) {
if (force_update || textsensortype->get_needs_to_send_update())
textsensortype->send_state_to_nextion();
}
}
void Nextion::update_components_by_prefix(const std::string &prefix) {
for (auto *binarysensortype : this->binarysensortype_) {
if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos)
binarysensortype->update_component_settings(true);
}
for (auto *sensortype : this->sensortype_) {
if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos)
sensortype->update_component_settings(true);
}
for (auto *switchtype : this->switchtype_) {
if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos)
switchtype->update_component_settings(true);
}
for (auto *textsensortype : this->textsensortype_) {
if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos)
textsensortype->update_component_settings(true);
}
}
uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
uint16_t ret = 0;
uint8_t c = 0;
uint8_t nr_of_ff_bytes = 0;
uint64_t start;
bool exit_flag = false;
bool ff_flag = false;
start = millis();
while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
this->read_byte(&c);
if (c == 0xFF)
nr_of_ff_bytes++;
else {
nr_of_ff_bytes = 0;
ff_flag = false;
}
if (nr_of_ff_bytes >= 3)
ff_flag = true;
response += (char) c;
if (recv_flag) {
if (response.find(0x05) != std::string::npos) {
exit_flag = true;
}
}
App.feed_wdt();
delay(1);
if (exit_flag || ff_flag) {
break;
}
}
return false;
if (ff_flag)
response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
ret = response.length();
return ret;
}
void Nextion::loop() {
while (this->available() >= 4) {
this->read_until_ack_();
/**
* @brief
*
* @param variable_name Name for the queue
*/
void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
nextion_queue->component = new nextion::NextionComponentBase;
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = millis();
this->nextion_queue_.push_back(nextion_queue);
ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str());
}
/**
* @brief
*
* @param variable_name Variable name for the queue
* @param command
*/
void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) {
if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty())
return;
if (this->send_command_(command)) {
this->add_no_result_to_queue_(variable_name);
}
}
#ifdef USE_TIME
void Nextion::set_nextion_rtc_time(time::ESPTime time) {
this->send_command_printf("rtc0=%u", time.year);
this->send_command_printf("rtc1=%u", time.month);
this->send_command_printf("rtc2=%u", time.day_of_month);
this->send_command_printf("rtc3=%u", time.hour);
this->send_command_printf("rtc4=%u", time.minute);
this->send_command_printf("rtc5=%u", time.second);
}
#endif
void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); }
void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); }
bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,
...) {
if ((!this->is_setup() && !this->ignore_is_setup_))
return false;
void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
char buffer[256];
va_list arg;
va_start(arg, format);
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (ret > 0)
this->set_component_text(component, buffer);
}
void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; }
if (ret <= 0) {
ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
return false;
}
void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) {
if (this->page_id_ == page_id && this->component_id_ == component_id) {
this->publish_state(on);
this->add_no_result_to_queue_with_command_(variable_name, buffer);
return true;
}
/**
* @brief Sends a formatted command to the nextion
*
* @param variable_name Variable name for the queue
* @param format The printf-style command format, like "vis %s,0"
* @param ... The format arguments
*/
bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) {
if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
return false;
char buffer[256];
va_list arg;
va_start(arg, format);
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (ret <= 0) {
ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
return false;
}
this->add_no_result_to_queue_with_command_(variable_name, buffer);
return true;
}
/**
* @brief
*
* @param variable_name Variable name for the queue
* @param variable_name_to_send Variable name for the left of the command
* @param state_value Value to set
* @param is_sleep_safe The command is safe to send when the Nextion is sleeping
*/
void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) {
this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
state_value);
}
void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
const std::string &variable_name_to_send, int state_value) {
this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
}
void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
const std::string &variable_name_to_send, int state_value,
bool is_sleep_safe) {
if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
return;
this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(),
state_value);
}
/**
* @brief
*
* @param variable_name Variable name for the queue
* @param variable_name_to_send Variable name for the left of the command
* @param state_value Sting value to set
* @param is_sleep_safe The command is safe to send when the Nextion is sleeping
*/
void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) {
this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
state_value);
}
void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
const std::string &variable_name_to_send,
const std::string &state_value) {
this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
}
void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
const std::string &variable_name_to_send,
const std::string &state_value, bool is_sleep_safe) {
if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
return;
this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(),
state_value.c_str());
}
void Nextion::add_to_get_queue(NextionComponentBase *component) {
if ((!this->is_setup() && !this->ignore_is_setup_))
return;
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
nextion_queue->component = component;
nextion_queue->queue_time = millis();
ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
std::string command = "get " + component->get_variable_name_to_send();
if (this->send_command_(command)) {
this->nextion_queue_.push_back(nextion_queue);
}
}
/**
* @brief Add addt command to the queue
*
* @param component_id The waveform component id
* @param wave_chan_id The waveform channel to send it to
* @param buffer_to_send The buffer size
* @param buffer_size The buffer data
*/
void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
return;
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
nextion_queue->component = new nextion::NextionComponentBase;
nextion_queue->queue_time = millis();
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
: 255; // ADDT command can only send 255
std::string command = "addt " + to_string(component->get_component_id()) + "," +
to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
if (this->send_command_(command)) {
this->nextion_queue_.push_back(nextion_queue);
}
}
void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect")
void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); }
} // namespace nextion
} // namespace esphome

View File

@ -1,9 +1,21 @@
#pragma once
#include "esphome/core/component.h"
#include <deque>
#include "esphome/core/defines.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "nextion_base.h"
#include "nextion_component.h"
#include "esphome/components/display/display_color_utils.h"
#if defined(USE_ETHERNET) || defined(USE_WIFI)
#ifdef ARDUINO_ARCH_ESP32
#include <HTTPClient.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#endif
#endif
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
@ -12,12 +24,14 @@
namespace esphome {
namespace nextion {
class NextionTouchComponent;
class Nextion;
class NextionComponentBase;
using nextion_writer_t = std::function<void(Nextion &)>;
class Nextion : public PollingComponent, public uart::UARTDevice {
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice {
public:
/**
* Set the text of a component to a static string.
@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* This will change the image of the component `pic` to the image with ID `4`.
*/
void set_component_picture(const char *component, const char *picture) {
this->send_command_printf("%s.val=%s", component, picture);
}
void set_component_picture(const char *component, const char *picture);
/**
* Set the background color of a component.
* @param component The component name.
* @param color The color (as a uint32_t).
*
* Example:
* ```cpp
* it.set_component_background_color("button", 0xFF0000);
* ```
*
* This will change the background color of the component `button` to red.
*/
void set_component_background_color(const char *component, uint32_t color);
/**
* Set the background color of a component.
* @param component The component name.
@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Example:
* ```cpp
* it.set_component_background_color("button", "17013");
* it.set_component_background_color("button", "RED");
* ```
*
* This will change the background color of the component `button` to blue.
@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* Nextion HMI colors.
*/
void set_component_background_color(const char *component, const char *color);
/**
* Set the background color of a component.
* @param component The component name.
* @param color The color (as Color).
*
* Example:
* ```cpp
* it.set_component_background_color("button", color);
* ```
*
* This will change the background color of the component `button` to what color contains.
*/
void set_component_background_color(const char *component, Color color) override;
/**
* Set the pressed background color of a component.
* @param component The component name.
* @param color The color (as a int).
*
* Example:
* ```cpp
* it.set_component_pressed_background_color("button", 0xFF0000 );
* ```
*
* This will change the pressed background color of the component `button` to red. This is the background color that
* is shown when the component is pressed.
*/
void set_component_pressed_background_color(const char *component, uint32_t color);
/**
* Set the pressed background color of a component.
* @param component The component name.
@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Example:
* ```cpp
* it.set_component_pressed_background_color("button", "17013");
* it.set_component_pressed_background_color("button", "RED");
* ```
*
* This will change the pressed background color of the component `button` to blue. This is the background color that
@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* colors.
*/
void set_component_pressed_background_color(const char *component, const char *color);
/**
* Set the pressed background color of a component.
* @param component The component name.
* @param color The color (as Color).
*
* Example:
* ```cpp
* it.set_component_pressed_background_color("button", color);
* ```
*
* This will change the pressed background color of the component `button` to blue. This is the background color that
* is shown when the component is pressed. Use this [color
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
* colors.
*/
void set_component_pressed_background_color(const char *component, Color color) override;
/**
* Set the picture id of a component.
* @param component The component name.
* @param pic_id The picture ID.
*
* Example:
* ```cpp
* it.set_component_pic("textview", 1);
* ```
*
* This will change the picture id of the component `textview`.
*/
void set_component_pic(const char *component, uint8_t pic_id);
/**
* Set the background picture id of component.
* @param component The component name.
* @param pic_id The picture ID.
*
* Example:
* ```cpp
* it.set_component_picc("textview", 1);
* ```
*
* This will change the background picture id of the component `textview`.
*/
void set_component_picc(const char *component, uint8_t pic_id);
/**
* Set the font color of a component.
* @param component The component name.
* @param color The color (as a uint32_t ).
*
* Example:
* ```cpp
* it.set_component_font_color("textview", 0xFF0000);
* ```
*
* This will change the font color of the component `textview` to a red color.
*/
void set_component_font_color(const char *component, uint32_t color);
/**
* Set the font color of a component.
* @param component The component name.
@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Example:
* ```cpp
* it.set_component_font_color("textview", "17013");
* it.set_component_font_color("textview", "RED");
* ```
*
* This will change the font color of the component `textview` to a blue color.
@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* Nextion HMI colors.
*/
void set_component_font_color(const char *component, const char *color);
/**
* Set the font color of a component.
* @param component The component name.
* @param color The color (as Color).
*
* Example:
* ```cpp
* it.set_component_font_color("textview", color);
* ```
*
* This will change the font color of the component `textview` to a blue color.
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
* Nextion HMI colors.
*/
void set_component_font_color(const char *component, Color color) override;
/**
* Set the pressed font color of a component.
* @param component The component name.
* @param color The color (as a uint32_t).
*
* Example:
* ```cpp
* it.set_component_pressed_font_color("button", 0xFF0000);
* ```
*
* This will change the pressed font color of the component `button` to a red.
*/
void set_component_pressed_font_color(const char *component, uint32_t color);
/**
* Set the pressed font color of a component.
* @param component The component name.
@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Example:
* ```cpp
* it.set_component_pressed_font_color("button", "17013");
* it.set_component_pressed_font_color("button", "RED");
* ```
*
* This will change the pressed font color of the component `button` to a blue color.
@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* Nextion HMI colors.
*/
void set_component_pressed_font_color(const char *component, const char *color);
/**
* Set the pressed font color of a component.
* @param component The component name.
* @param color The color (as Color).
*
* Example:
* ```cpp
* it.set_component_pressed_font_color("button", color);
* ```
*
* This will change the pressed font color of the component `button` to a blue color.
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
* Nextion HMI colors.
*/
void set_component_pressed_font_color(const char *component, Color color) override;
/**
* Set the coordinates of a component on screen.
* @param component The component name.
@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor.
*/
void set_component_font(const char *component, uint8_t font_id);
void set_component_font(const char *component, uint8_t font_id) override;
#ifdef USE_TIME
/**
* Send the current time to the nextion display.
@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Hides the component named `button`.
*/
void hide_component(const char *component);
void hide_component(const char *component) override;
/**
* Show a component.
* @param component The component name.
@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Shows the component named `button`.
*/
void show_component(const char *component);
void show_component(const char *component) override;
/**
* Enable touch for a component.
* @param component The component name.
@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* @param value The value to write.
*/
void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value);
void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value);
/**
* Display a picture at coordinates.
* @param picture_id The picture id.
@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*
* Example:
* ```cpp
* fill_area(50, 50, 100, 100, "17013");
* fill_area(50, 50, 100, 100, "RED");
* ```
*
* Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* convert color codes to Nextion HMI colors
*/
void fill_area(int x1, int y1, int width, int height, const char *color);
/**
* Fill a rectangle with a color.
* @param x1 The starting x coordinate.
* @param y1 The starting y coordinate.
* @param width The width to draw.
* @param height The height to draw.
* @param color The color to draw with (as Color).
*
* Example:
* ```cpp
* fill_area(50, 50, 100, 100, color);
* ```
*
* Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
* the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to
* convert color codes to Nextion HMI colors
*/
void fill_area(int x1, int y1, int width, int height, Color color);
/**
* Draw a line on the screen.
* @param x1 The starting x coordinate.
@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* colors.
*/
void line(int x1, int y1, int x2, int y2, const char *color);
/**
* Draw a line on the screen.
* @param x1 The starting x coordinate.
* @param y1 The starting y coordinate.
* @param x2 The ending x coordinate.
* @param y2 The ending y coordinate.
* @param color The color to draw with (as Color).
*
* Example:
* ```cpp
* it.line(50, 50, 75, 75, "17013");
* ```
*
* Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate
* `75` with the color of blue. Use this [color
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
* colors.
*/
void line(int x1, int y1, int x2, int y2, Color color);
/**
* Draw a rectangle outline.
* @param x1 The starting x coordinate.
@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* colors.
*/
void rectangle(int x1, int y1, int width, int height, const char *color);
/**
* Draw a rectangle outline.
* @param x1 The starting x coordinate.
* @param y1 The starting y coordinate.
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @param color The color to draw with (as Color).
*
* Example:
* ```cpp
* it.rectangle(25, 35, 40, 50, "17013");
* ```
*
* Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a
* length of `50` with color of blue. Use this [color
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
* colors.
*/
void rectangle(int x1, int y1, int width, int height, Color color);
/**
* Draw a circle outline
* @param center_x The center x coordinate.
@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* @param color The color to draw with (as a string).
*/
void circle(int center_x, int center_y, int radius, const char *color);
/**
* Draw a circle outline
* @param center_x The center x coordinate.
* @param center_y The center y coordinate.
* @param radius The circle radius.
* @param color The color to draw with (as Color).
*/
void circle(int center_x, int center_y, int radius, Color color);
/**
* Draw a filled circled.
* @param center_x The center x coordinate.
@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* Nextion HMI colors.
*/
void filled_circle(int center_x, int center_y, int radius, const char *color);
/** Set the brightness of the backlight.
*
* @param brightness The brightness, from 0 to 100.
/**
* Draw a filled circled.
* @param center_x The center x coordinate.
* @param center_y The center y coordinate.
* @param radius The circle radius.
* @param color The color to draw with (as Color).
*
* Example:
* ```cpp
* it.set_backlight_brightness(30);
* it.filled_cricle(25, 25, 10, color);
* ```
*
* Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue.
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
* Nextion HMI colors.
*/
void filled_circle(int center_x, int center_y, int radius, Color color);
/** Set the brightness of the backlight.
*
* @param brightness The brightness percentage from 0 to 1.0.
*
* Example:
* ```cpp
* it.set_backlight_brightness(.3);
* ```
*
* Changes the brightness of the display to 30%.
*/
void set_backlight_brightness(uint8_t brightness);
void set_backlight_brightness(float brightness);
/**
* Set the touch sleep timeout of the display.
* @param timeout Timeout in seconds.
@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
* `thup`.
*/
void set_touch_sleep_timeout(uint16_t timeout);
/**
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_wake_up_page(2);
* ```
*
* The display will wake up to page 2.
*/
void set_wake_up_page(uint8_t page_id = 255);
/**
* Sets if Nextion should auto-wake from sleep when touch press occurs.
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
*
* Example:
* ```cpp
* it.set_auto_wake_on_touch(true);
* ```
*
* The display will wake up by touch.
*/
void set_auto_wake_on_touch(bool auto_wake);
/**
* Sets Nextion mode between sleep and awake
* @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode.
*/
void sleep(bool sleep);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); }
void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); }
void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); }
void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); }
void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); }
void setup() override;
void set_brightness(float brightness) { this->brightness_ = brightness; }
float get_setup_priority() const override;
@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
void loop() override;
void set_writer(const nextion_writer_t &writer);
/**
* Manually send a raw command to the display and don't wait for an acknowledgement packet.
* @param command The command to write, for example "vis b0,0".
*/
void send_command_no_ack(const char *command);
// This function has been deprecated
void set_wait_for_ack(bool wait_for_ack);
/**
* Manually send a raw formatted command to the display.
* @param format The printf-style command format, like "vis %s,0"
@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
*/
bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
void set_wait_for_ack(bool wait_for_ack);
#ifdef USE_TFT_UPLOAD
/**
* Set the tft file URL. https seems problamtic with arduino..
*/
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
#endif
/**
* Upload the tft file and softreset the Nextion
*/
void upload_tft();
void dump_config() override;
/**
* Softreset the Nextion
*/
void soft_reset();
/** Add a callback to be notified of sleep state changes.
*
* @param callback The void() callback.
*/
void add_sleep_state_callback(std::function<void()> &&callback);
/** Add a callback to be notified of wake state changes.
*
* @param callback The void() callback.
*/
void add_wake_state_callback(std::function<void()> &&callback);
/** Add a callback to be notified when the nextion completes its initialize setup.
*
* @param callback The void() callback.
*/
void add_setup_state_callback(std::function<void()> &&callback);
void update_all_components();
/**
* @brief Set the nextion sensor state object.
*
* @param[in] queue_type
* Index of NextionQueueType.
*
* @param[in] name
* Component/variable name.
*
* @param[in] state
* State to set.
*/
void set_nextion_sensor_state(int queue_type, const std::string &name, float state);
void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state);
void set_nextion_text_state(const std::string &name, const std::string &state);
void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override;
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
int state_value) override;
void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override;
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
const std::string &state_value) override;
void add_to_get_queue(NextionComponentBase *component) override;
void add_addt_command_to_queue(NextionComponentBase *component) override;
void update_components_by_prefix(const std::string &prefix);
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
this->touch_sleep_timeout_ = touch_sleep_timeout;
}
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; }
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
protected:
bool ack_();
bool read_until_ack_();
std::deque<NextionQueue *> nextion_queue_;
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
void all_components_send_state_(bool force_update = false);
uint64_t comok_sent_ = 0;
bool remove_from_q_(bool report_empty = true);
/**
* @brief
* Sends commands ignoring of the Nextion has been setup.
*/
bool ignore_is_setup_ = false;
bool nextion_reports_is_setup_ = false;
uint8_t nextion_event_;
void process_nextion_commands_();
void process_serial_();
bool is_updating_ = false;
uint32_t touch_sleep_timeout_ = 0;
int wake_up_page_ = -1;
bool auto_wake_on_touch_ = true;
/**
* Manually send a raw command to the display and don't wait for an acknowledgement packet.
* @param command The command to write, for example "vis b0,0".
*/
bool send_command_(const std::string &command);
void add_no_result_to_queue_(const std::string &variable_name);
bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...)
__attribute__((format(printf, 3, 4)));
void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command);
bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...)
__attribute__((format(printf, 3, 4)));
void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
const std::string &variable_name_to_send, int state_value,
bool is_sleep_safe = false);
void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
const std::string &variable_name_to_send,
const std::string &state_value, bool is_sleep_safe = false);
#ifdef USE_TFT_UPLOAD
#if defined(USE_ETHERNET) || defined(USE_WIFI)
#ifdef ARDUINO_ARCH_ESP8266
WiFiClient *wifi_client_{nullptr};
BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr};
WiFiClient *get_wifi_client_();
#endif
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param int contentLength Total size of the file
* @param uint32_t chunk_size
* @return true if success, false for failure.
*/
int content_length_ = 0;
int tft_size_ = 0;
int upload_by_chunks_(HTTPClient *http, int range_start);
bool upload_with_range_(uint32_t range_start, uint32_t range_end);
/**
* start update tft file to nextion.
*
* @param const uint8_t *file_buf
* @param size_t buf_size
* @return true if success, false for failure.
*/
bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size);
void upload_end_();
#endif
#endif
bool get_is_connected_() { return this->is_connected_; }
bool check_connect_();
std::vector<NextionComponentBase *> touch_;
std::vector<NextionComponentBase *> switchtype_;
std::vector<NextionComponentBase *> sensortype_;
std::vector<NextionComponentBase *> textsensortype_;
std::vector<NextionComponentBase *> binarysensortype_;
CallbackManager<void()> setup_callback_{};
CallbackManager<void()> sleep_callback_{};
CallbackManager<void()> wake_callback_{};
std::vector<NextionTouchComponent *> touch_;
optional<nextion_writer_t> writer_;
bool wait_for_ack_{true};
float brightness_{1.0};
std::string device_model_;
std::string firmware_version_;
std::string serial_number_;
std::string flash_size_;
void remove_front_no_sensors_();
#ifdef USE_TFT_UPLOAD
std::string tft_url_;
uint8_t *transfer_buffer_{nullptr};
size_t transfer_buffer_size_;
bool upload_first_chunk_sent_ = false;
#endif
#ifdef NEXTION_PROTOCOL_LOG
void print_queue_members_();
#endif
void reset_(bool reset_nextion = true);
std::string command_data_;
bool is_connected_ = false;
uint32_t startup_override_ms_ = 8000;
uint32_t max_q_age_ms_ = 8000;
uint32_t started_ms_ = 0;
bool sent_setup_commands_ = false;
};
class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff {
public:
void set_page_id(uint8_t page_id) { page_id_ = page_id; }
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
void process(uint8_t page_id, uint8_t component_id, bool on);
protected:
uint8_t page_id_;
uint8_t component_id_;
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,58 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/color.h"
#include "nextion_component_base.h"
namespace esphome {
namespace nextion {
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
#define NEXTION_PROTOCOL_LOG
#endif
#ifdef NEXTION_PROTOCOL_LOG
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__)
#else
#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__)
#endif
#else
#define ESP_LOGN(tag, ...) \
{}
#endif
class NextionBase;
class NextionBase {
public:
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0;
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
const std::string &variable_name_to_send, int state_value) = 0;
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0;
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
const std::string &variable_name_to_send,
const std::string &state_value) = 0;
virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0;
virtual void add_to_get_queue(NextionComponentBase *component) = 0;
virtual void set_component_background_color(const char *component, Color color) = 0;
virtual void set_component_pressed_background_color(const char *component, Color color) = 0;
virtual void set_component_font_color(const char *component, Color color) = 0;
virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
virtual void set_component_font(const char *component, uint8_t font_id) = 0;
virtual void show_component(const char *component) = 0;
virtual void hide_component(const char *component) = 0;
bool is_sleeping() { return this->is_sleeping_; }
bool is_setup() { return this->is_setup_; }
protected:
bool is_setup_ = false;
bool is_sleeping_ = false;
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,234 @@
#include "nextion.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion";
// Sleep safe commands
void Nextion::soft_reset() { this->send_command_("rest"); }
void Nextion::set_wake_up_page(uint8_t page_id) {
if (page_id > 255) {
ESP_LOGD(TAG, "Wake up page of bounds, range 0-255");
return;
}
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
}
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
if (timeout < 3 || timeout > 65535) {
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
return;
}
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
}
void Nextion::sleep(bool sleep) {
if (sleep) { // Set sleep
this->is_sleeping_ = true;
this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true);
} else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off
this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true);
}
}
// End sleep safe commands
// Set Colors
void Nextion::set_component_background_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color);
}
void Nextion::set_component_background_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color);
}
void Nextion::set_component_background_color(const char *component, Color color) {
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component,
display::ColorUtil::color_to_565(color));
}
void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color);
}
void Nextion::set_component_pressed_background_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color);
}
void Nextion::set_component_pressed_background_color(const char *component, Color color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component,
display::ColorUtil::color_to_565(color));
}
void Nextion::set_component_pic(const char *component, uint8_t pic_id) {
this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id);
}
void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id);
}
void Nextion::set_component_font_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color);
}
void Nextion::set_component_font_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color);
}
void Nextion::set_component_font_color(const char *component, Color color) {
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component,
display::ColorUtil::color_to_565(color));
}
void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color);
}
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color);
}
void Nextion::set_component_pressed_font_color(const char *component, Color color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component,
display::ColorUtil::color_to_565(color));
}
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
va_list arg;
va_start(arg, format);
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (ret > 0)
this->set_component_text(component, buffer);
}
// General Nextion
void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); }
void Nextion::set_backlight_brightness(float brightness) {
if (brightness < 0 || brightness > 1.0) {
ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0");
return;
}
this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast<int>(brightness * 100));
}
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
}
// General Component
void Nextion::set_component_font(const char *component, uint8_t font_id) {
this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id);
}
void Nextion::hide_component(const char *component) {
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
}
void Nextion::show_component(const char *component) {
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
}
void Nextion::enable_component_touch(const char *component) {
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);
}
void Nextion::disable_component_touch(const char *component) {
this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component);
}
void Nextion::set_component_picture(const char *component, const char *picture) {
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture);
}
void Nextion::set_component_text(const char *component, const char *text) {
this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text);
}
void Nextion::set_component_value(const char *component, int value) {
this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value);
}
void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) {
this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value);
}
void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) {
this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number,
value);
}
void Nextion::set_component_coordinates(const char *component, int x, int y) {
this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x);
this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y);
}
// Drawing
void Nextion::display_picture(int picture_id, int x_start, int y_start) {
this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id);
}
void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) {
this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color);
}
void Nextion::fill_area(int x1, int y1, int width, int height, Color color) {
this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height,
display::ColorUtil::color_to_565(color));
}
void Nextion::line(int x1, int y1, int x2, int y2, const char *color) {
this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color);
}
void Nextion::line(int x1, int y1, int x2, int y2, Color color) {
this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2,
display::ColorUtil::color_to_565(color));
}
void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) {
this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color);
}
void Nextion::rectangle(int x1, int y1, int width, int height, Color color) {
this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height,
display::ColorUtil::color_to_565(color));
}
void Nextion::circle(int center_x, int center_y, int radius, const char *color) {
this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color);
}
void Nextion::circle(int center_x, int center_y, int radius, Color color) {
this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius,
display::ColorUtil::color_to_565(color));
}
void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) {
this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color);
}
void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) {
this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius,
display::ColorUtil::color_to_565(color));
}
#ifdef USE_TIME
void Nextion::set_nextion_rtc_time(time::ESPTime time) {
this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month);
this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour);
this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute);
this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second);
}
#endif
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,116 @@
#include "nextion_component.h"
namespace esphome {
namespace nextion {
void NextionComponent::set_background_color(Color bco) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->bco_ = bco;
this->bco_needs_update_ = true;
this->bco_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::set_background_pressed_color(Color bco2) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->bco2_ = bco2;
this->bco2_needs_update_ = true;
this->bco2_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::set_foreground_color(Color pco) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->pco_ = pco;
this->pco_needs_update_ = true;
this->pco_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::set_foreground_pressed_color(Color pco2) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->pco2_ = pco2;
this->pco2_needs_update_ = true;
this->pco2_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::set_font_id(uint8_t font_id) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->font_id_ = font_id;
this->font_id_needs_update_ = true;
this->font_id_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::set_visible(bool visible) {
if (this->variable_name_ == this->variable_name_to_send_) {
return; // This is a variable. no need to set color
}
this->visible_ = visible;
this->visible_needs_update_ = true;
this->visible_is_set_ = true;
this->update_component_settings();
}
void NextionComponent::update_component_settings(bool force_update) {
if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ ||
(!this->visible_needs_update_ && !this->visible_)) {
this->needs_to_send_update_ = true;
return;
}
if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) {
std::string name_to_send = this->variable_name_;
size_t pos = name_to_send.find_last_of('.');
if (pos != std::string::npos) {
name_to_send = name_to_send.substr(pos + 1);
}
this->visible_needs_update_ = false;
if (this->visible_) {
this->nextion_->show_component(name_to_send.c_str());
this->send_state_to_nextion();
} else {
this->nextion_->hide_component(name_to_send.c_str());
return;
}
}
if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) {
this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_);
this->bco_needs_update_ = false;
}
if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) {
this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_);
this->bco2_needs_update_ = false;
}
if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) {
this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_);
this->pco_needs_update_ = false;
}
if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) {
this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_);
this->pco2_needs_update_ = false;
}
if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) {
this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_);
this->font_id_needs_update_ = false;
}
}
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,49 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/color.h"
#include "nextion_base.h"
namespace esphome {
namespace nextion {
class NextionComponent;
class NextionComponent : public NextionComponentBase {
public:
void update_component_settings() override { this->update_component_settings(false); };
void update_component_settings(bool force_update) override;
void set_background_color(Color bco);
void set_background_pressed_color(Color bco2);
void set_foreground_color(Color pco);
void set_foreground_pressed_color(Color pco2);
void set_font_id(uint8_t font_id);
void set_visible(bool visible);
protected:
NextionBase *nextion_;
bool bco_needs_update_ = false;
bool bco_is_set_ = false;
Color bco_;
bool bco2_needs_update_ = false;
bool bco2_is_set_ = false;
Color bco2_;
bool pco_needs_update_ = false;
bool pco_is_set_ = false;
Color pco_;
bool pco2_needs_update_ = false;
bool pco2_is_set_ = false;
Color pco2_;
uint8_t font_id_ = 0;
bool font_id_needs_update_ = false;
bool font_id_is_set_ = false;
bool visible_ = true;
bool visible_needs_update_ = false;
bool visible_is_set_ = false;
// void send_state_to_nextion() = 0;
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,95 @@
#pragma once
#include <utility>
#include "esphome/core/defines.h"
namespace esphome {
namespace nextion {
enum NextionQueueType {
NO_RESULT = 0,
SENSOR = 1,
BINARY_SENSOR = 2,
SWITCH = 3,
TEXT_SENSOR = 4,
WAVEFORM_SENSOR = 5,
};
static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR",
"SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"};
class NextionComponentBase;
class NextionQueue {
public:
virtual ~NextionQueue() = default;
NextionComponentBase *component;
uint32_t queue_time = 0;
};
class NextionComponentBase {
public:
virtual ~NextionComponentBase() = default;
void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") {
variable_name_ = variable_name;
if (variable_name_to_send.empty()) {
variable_name_to_send_ = variable_name;
} else {
variable_name_to_send_ = variable_name_to_send;
}
}
virtual void update_component_settings(){};
virtual void update_component_settings(bool force_update){};
virtual void update_component(){};
virtual void process_sensor(const std::string &variable_name, int state){};
virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){};
virtual void process_text(const std::string &variable_name, const std::string &text_value){};
virtual void process_bool(const std::string &variable_name, bool on){};
virtual void set_state(float state){};
virtual void set_state(float state, bool publish){};
virtual void set_state(float state, bool publish, bool send_to_nextion){};
virtual void set_state(bool state){};
virtual void set_state(bool state, bool publish){};
virtual void set_state(bool state, bool publish, bool send_to_nextion){};
virtual void set_state(const std::string &state) {}
virtual void set_state(const std::string &state, bool publish) {}
virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){};
uint8_t get_component_id() { return this->component_id_; }
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
uint8_t get_wave_channel_id() { return this->wave_chan_id_; }
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
std::vector<uint8_t> get_wave_buffer() { return this->wave_buffer_; }
size_t get_wave_buffer_size() { return this->wave_buffer_.size(); }
std::string get_variable_name() { return this->variable_name_; }
std::string get_variable_name_to_send() { return this->variable_name_to_send_; }
virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; }
virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; }
virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){};
virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){};
virtual void send_state_to_nextion(){};
bool get_needs_to_send_update() { return this->needs_to_send_update_; }
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
protected:
std::string variable_name_;
std::string variable_name_to_send_;
uint8_t component_id_ = 0;
uint8_t wave_chan_id_ = UINT8_MAX;
std::vector<uint8_t> wave_buffer_;
int wave_max_length_ = 255;
bool needs_to_send_update_;
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,343 @@
#include "nextion.h"
#include "esphome/core/application.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_upload";
#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI))
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) {
int range_end = 0;
if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip
range_end = 16384 - 1;
} else {
range_end = range_start + this->transfer_buffer_size_ - 1;
}
if (range_end > this->tft_size_)
range_end = this->tft_size_;
bool begin_status = false;
#ifdef ARDUINO_ARCH_ESP32
begin_status = http->begin(this->tft_url_.c_str());
#endif
#ifdef ARDUINO_ARCH_ESP8266
#ifndef CLANG_TIDY
http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http->setRedirectLimit(3);
begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
#endif
#endif
char range_header[64];
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
ESP_LOGD(TAG, "Requesting range: %s", range_header);
int tries = 1;
int code = 0;
while (tries <= 5) {
#ifdef ARDUINO_ARCH_ESP32
begin_status = http->begin(this->tft_url_.c_str());
#endif
#ifndef CLANG_TIDY
#ifdef ARDUINO_ARCH_ESP8266
begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
#endif
#endif
++tries;
if (!begin_status) {
ESP_LOGD(TAG, "upload_by_chunks_: connection failed");
continue;
}
http->addHeader("Range", range_header);
code = http->GET();
if (code == 200 || code == 206) {
break;
}
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
http->end();
App.feed_wdt();
delay(500); // NOLINT
}
if (tries > 5) {
return -1;
}
std::string recv_string;
size_t size = 0;
int sent = 0;
int range = range_end - range_start;
while (sent < range) {
size = http->getStreamPtr()->available();
if (!size) {
App.feed_wdt();
delay(0);
continue;
}
int c = http->getStreamPtr()->readBytes(
&this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size));
sent += c;
}
http->end();
ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent);
for (uint32_t i = 0; i < range; i += 4096) {
this->write_array(&this->transfer_buffer_[i], 4096);
this->content_length_ -= 4096;
ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range,
range_end, range_start);
if (!this->upload_first_chunk_sent_) {
this->upload_first_chunk_sent_ = true;
delay(500); // NOLINT
App.feed_wdt();
}
this->recv_ret_string_(recv_string, 2048, true);
if (recv_string[0] == 0x08) {
uint32_t result = 0;
for (int i = 0; i < 4; ++i) {
result += static_cast<uint8_t>(recv_string[i + 1]) << (8 * i);
}
if (result > 0) {
ESP_LOGD(TAG, "Nextion reported new range %d", result);
this->content_length_ = this->tft_size_ - result;
return result;
}
}
recv_string.clear();
}
return range_end + 1;
}
void Nextion::upload_tft() {
if (this->is_updating_) {
ESP_LOGD(TAG, "Currently updating");
return;
}
if (!network_is_connected()) {
ESP_LOGD(TAG, "network is not connected");
return;
}
this->is_updating_ = true;
HTTPClient http;
http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
bool begin_status = false;
#ifdef ARDUINO_ARCH_ESP32
begin_status = http.begin(this->tft_url_.c_str());
#endif
#ifdef ARDUINO_ARCH_ESP8266
#ifndef CLANG_TIDY
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setRedirectLimit(3);
begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
#endif
#endif
if (!begin_status) {
this->is_updating_ = false;
ESP_LOGD(TAG, "connection failed");
#ifdef ARDUINO_ARCH_ESP32
if (psramFound())
free(this->transfer_buffer_);
else
#endif
delete this->transfer_buffer_;
return;
} else {
ESP_LOGD(TAG, "Connected");
}
http.addHeader("Range", "bytes=0-255");
const char *header_names[] = {"Content-Range"};
http.collectHeaders(header_names, 1);
ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str());
http.setReuse(true);
// try up to 5 times. DNS sometimes needs a second try or so
int tries = 1;
int code = http.GET();
delay(100); // NOLINT
App.feed_wdt();
while (code != 200 && code != 206 && tries <= 5) {
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
delay(250); // NOLINT
App.feed_wdt();
code = http.GET();
++tries;
}
if ((code != 200 && code != 206) || tries > 5) {
this->upload_end_();
}
String content_range_string = http.header("Content-Range");
content_range_string.remove(0, 12);
this->content_length_ = content_range_string.toInt();
this->tft_size_ = content_length_;
http.end();
if (this->content_length_ < 4096) {
ESP_LOGE(TAG, "Failed to get file size");
this->upload_end_();
}
ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str());
// The Nextion will ignore the update command if it is sleeping
this->send_command_("sleep=0");
this->set_backlight_brightness(1.0);
delay(250); // NOLINT
App.feed_wdt();
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate());
// Clear serial receive buffer
uint8_t d;
while (this->available()) {
this->read_byte(&d);
};
this->send_command_(command);
App.feed_wdt();
std::string response;
ESP_LOGD(TAG, "Waiting for upgrade response");
this->recv_ret_string_(response, 2000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length());
for (int i = 0; i < response.length(); i++) {
ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]);
}
if (response.find(0x05) != std::string::npos) {
ESP_LOGD(TAG, "preparation for tft update done");
} else {
ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
this->upload_end_();
}
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
#ifdef ARDUINO_ARCH_ESP32
uint32_t chunk_size = 8192;
if (psramFound()) {
chunk_size = this->content_length_;
} else {
if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand
int chunk = int((ESP.getFreeHeap() - 32768) / 4096);
chunk_size = chunk * 4096;
chunk_size = chunk_size > 65536 ? 65536 : chunk_size;
} else if (ESP.getFreeHeap() < 10240) {
chunk_size = 4096;
}
}
#else
uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192;
#endif
if (this->transfer_buffer_ == nullptr) {
#ifdef ARDUINO_ARCH_ESP32
if (psramFound()) {
ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram());
this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size);
if (this->transfer_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size);
this->upload_end_();
}
} else {
#endif
ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
this->transfer_buffer_ = new uint8_t[chunk_size];
if (!this->transfer_buffer_) { // Try a smaller size
ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
chunk_size = 4096;
ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
this->transfer_buffer_ = new uint8_t[chunk_size];
if (!this->transfer_buffer_)
this->upload_end_();
#ifdef ARDUINO_ARCH_ESP32
}
#endif
}
this->transfer_buffer_size_ = chunk_size;
}
ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap());
int result = 0;
while (this->content_length_ > 0) {
result = this->upload_by_chunks_(&http, result);
if (result < 0) {
ESP_LOGD(TAG, "Error updating Nextion!");
this->upload_end_();
}
App.feed_wdt();
ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_);
}
ESP_LOGD(TAG, "Succesfully updated Nextion!");
this->upload_end_();
}
void Nextion::upload_end_() {
ESP_LOGD(TAG, "Restarting Nextion");
this->soft_reset();
delay(1500); // NOLINT
ESP_LOGD(TAG, "Restarting esphome");
ESP.restart();
}
#ifdef ARDUINO_ARCH_ESP8266
WiFiClient *Nextion::get_wifi_client_() {
if (this->tft_url_.compare(0, 6, "https:") == 0) {
if (this->wifi_client_secure_ == nullptr) {
this->wifi_client_secure_ = new BearSSL::WiFiClientSecure();
this->wifi_client_secure_->setInsecure();
this->wifi_client_secure_->setBufferSizes(512, 512);
}
return this->wifi_client_secure_;
}
if (this->wifi_client_ == nullptr) {
this->wifi_client_ = new WiFiClient();
}
return this->wifi_client_;
}
#endif
#else
void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); }
#endif
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,99 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
UNIT_EMPTY,
ICON_EMPTY,
CONF_COMPONENT_ID,
DEVICE_CLASS_EMPTY,
)
from .. import nextion_ns, CONF_NEXTION_ID
from ..base_component import (
setup_component_core_,
CONFIG_SENSOR_COMPONENT_SCHEMA,
CONF_VARIABLE_NAME,
CONF_COMPONENT_NAME,
CONF_PRECISION,
CONF_WAVE_CHANNEL_ID,
CONF_WAVE_MAX_VALUE,
CONF_WAVEFORM_SEND_LAST_VALUE,
CONF_WAVE_MAX_LENGTH,
)
CODEOWNERS = ["@senexcrenshaw"]
NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent)
def CheckWaveID(value):
value = cv.int_(value)
if value < 0 or value > 3:
raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3")
return value
def _validate(config):
if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config:
raise cv.Invalid(
f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set"
)
return config
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY)
.extend(
{
cv.GenerateID(): cv.declare_id(NextionSensor),
cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8),
cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID,
cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range(
min=1, max=1024
),
cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range(
min=1, max=1024
),
cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean,
}
)
.extend(CONFIG_SENSOR_COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("never")),
cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
_validate,
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_NEXTION_ID])
var = cg.new_Pvariable(config[CONF_ID], hub)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(hub.register_sensor_component(var))
await setup_component_core_(var, config, ".val")
if CONF_PRECISION in config:
cg.add(var.set_precision(config[CONF_PRECISION]))
if CONF_COMPONENT_ID in config:
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
if CONF_WAVE_CHANNEL_ID in config:
cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID]))
if CONF_WAVEFORM_SEND_LAST_VALUE in config:
cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE]))
if CONF_WAVE_MAX_VALUE in config:
cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE]))
if CONF_WAVE_MAX_LENGTH in config:
cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH]))

View File

@ -0,0 +1,110 @@
#include "nextion_sensor.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_sensor";
void NextionSensor::process_sensor(const std::string &variable_name, int state) {
if (!this->nextion_->is_setup())
return;
if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) {
this->publish_state(state);
ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state);
}
}
void NextionSensor::add_to_wave_buffer(float state) {
this->needs_to_send_update_ = true;
int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100);
wave_buffer_.push_back(wave_state);
if (this->wave_buffer_.size() > this->wave_max_length_) {
this->wave_buffer_.erase(this->wave_buffer_.begin());
}
}
void NextionSensor::update() {
if (!this->nextion_->is_setup())
return;
if (this->wave_chan_id_ == UINT8_MAX) {
this->nextion_->add_to_get_queue(this);
} else {
if (this->send_last_value_) {
this->add_to_wave_buffer(this->last_value_);
}
this->wave_update_();
}
}
void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
if (!this->nextion_->is_setup())
return;
if (isnan(state))
return;
if (this->wave_chan_id_ == UINT8_MAX) {
if (send_to_nextion) {
if (this->nextion_->is_sleeping() || !this->visible_) {
this->needs_to_send_update_ = true;
} else {
this->needs_to_send_update_ = false;
if (this->precision_ > 0) {
double to_multiply = pow(10, this->precision_);
int state_value = (int) (state * to_multiply);
this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value);
} else {
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
}
}
}
} else {
if (this->send_last_value_) {
this->last_value_ = state; // Update will handle setting the buffer
} else {
this->add_to_wave_buffer(state);
}
}
if (this->wave_chan_id_ == UINT8_MAX) {
if (publish) {
this->publish_state(state);
} else {
this->raw_state = state;
this->state = state;
this->has_state_ = true;
}
}
this->update_component_settings();
ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state);
}
void NextionSensor::wave_update_() {
if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) {
return;
}
#ifdef NEXTION_PROTOCOL_LOG
size_t buffer_to_send =
this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255
ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d",
buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_);
#endif
this->nextion_->add_addt_command_to_queue(this);
}
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,49 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "../nextion_component.h"
#include "../nextion_base.h"
namespace esphome {
namespace nextion {
class NextionSensor;
class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent {
public:
NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; }
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
void update_component() override { this->update(); }
void update() override;
void add_to_wave_buffer(float state);
void set_precision(uint8_t precision) { this->precision_ = precision; }
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; }
void process_sensor(const std::string &variable_name, int state) override;
void set_state(float state) override { this->set_state(state, true, true); }
void set_state(float state, bool publish) override { this->set_state(state, publish, true); }
void set_state(float state, bool publish, bool send_to_nextion) override;
void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; }
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
NextionQueueType get_queue_type() override {
return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
}
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
this->set_state(state_value, publish, send_to_nextion);
}
protected:
uint8_t precision_ = 0;
uint32_t wave_maxvalue_ = 255;
float last_value_ = 0;
bool send_last_value_ = true;
void wave_update_();
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import CONF_ID
from .. import nextion_ns, CONF_NEXTION_ID
from ..base_component import (
setup_component_core_,
CONF_COMPONENT_NAME,
CONF_VARIABLE_NAME,
CONFIG_SWITCH_COMPONENT_SCHEMA,
)
CODEOWNERS = ["@senexcrenshaw"]
NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent)
CONFIG_SCHEMA = cv.All(
switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(NextionSwitch),
}
)
.extend(CONFIG_SWITCH_COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("never")),
cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_NEXTION_ID])
var = cg.new_Pvariable(config[CONF_ID], hub)
await cg.register_component(var, config)
await switch.register_switch(var, config)
cg.add(hub.register_switch_component(var))
await setup_component_core_(var, config, ".val")

View File

@ -0,0 +1,52 @@
#include "nextion_switch.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_switch";
void NextionSwitch::process_bool(const std::string &variable_name, bool on) {
if (!this->nextion_->is_setup())
return;
if (this->variable_name_ == variable_name) {
this->publish_state(on);
ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
}
}
void NextionSwitch::update() {
if (!this->nextion_->is_setup())
return;
this->nextion_->add_to_get_queue(this);
}
void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) {
if (!this->nextion_->is_setup())
return;
if (send_to_nextion) {
if (this->nextion_->is_sleeping() || !this->visible_) {
this->needs_to_send_update_ = true;
} else {
this->needs_to_send_update_ = false;
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
}
}
if (publish) {
this->publish_state(state);
} else {
this->state = state;
}
this->update_component_settings();
ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state));
}
void NextionSwitch::write_state(bool state) { this->set_state(state); }
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
#include "../nextion_component.h"
#include "../nextion_base.h"
namespace esphome {
namespace nextion {
class NextionSwitch;
class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent {
public:
NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; }
void update() override;
void update_component() override { this->update(); }
void process_bool(const std::string &variable_name, bool on) override;
void set_state(bool state) override { this->set_state(state, true, true); }
void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
void set_state(bool state, bool publish, bool send_to_nextion) override;
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; }
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
this->set_state(state_value != 0, publish, send_to_nextion);
}
protected:
void write_state(bool state) override;
};
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,38 @@
from esphome.components import text_sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID
from .. import nextion_ns, CONF_NEXTION_ID
from ..base_component import (
setup_component_core_,
CONFIG_TEXT_COMPONENT_SCHEMA,
)
CODEOWNERS = ["@senexcrenshaw"]
NextionTextSensor = nextion_ns.class_(
"NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = (
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(NextionTextSensor),
}
)
.extend(CONFIG_TEXT_COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("never"))
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_NEXTION_ID])
var = cg.new_Pvariable(config[CONF_ID], hub)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
cg.add(hub.register_textsensor_component(var))
await setup_component_core_(var, config, ".txt")

View File

@ -0,0 +1,49 @@
#include "nextion_textsensor.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_textsensor";
void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) {
if (!this->nextion_->is_setup())
return;
if (this->variable_name_ == variable_name) {
this->publish_state(text_value);
ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str());
}
}
void NextionTextSensor::update() {
if (!this->nextion_->is_setup())
return;
this->nextion_->add_to_get_queue(this);
}
void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) {
if (!this->nextion_->is_setup())
return;
if (send_to_nextion) {
if (this->nextion_->is_sleeping() || !this->visible_) {
this->needs_to_send_update_ = true;
} else {
this->nextion_->add_no_result_to_queue_with_set(this, state);
}
}
if (publish) {
this->publish_state(state);
} else {
this->state = state;
this->has_state_ = true;
}
this->update_component_settings();
ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str());
}
} // namespace nextion
} // namespace esphome

View File

@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "../nextion_component.h"
#include "../nextion_base.h"
namespace esphome {
namespace nextion {
class NextionTextSensor;
class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent {
public:
NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; }
void update() override;
void update_component() override { this->update(); }
void on_state_changed(const std::string &state);
void process_text(const std::string &variable_name, const std::string &text_value) override;
void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); }
void set_state(const std::string &state) override { this->set_state(state, true, true); }
void set_state(const std::string &state, bool publish, bool send_to_nextion) override;
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; }
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {}
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {
this->set_state(state_value, publish, send_to_nextion);
}
};
} // namespace nextion
} // namespace esphome

View File

@ -56,6 +56,7 @@ class ESP8266SoftwareSerial {
class UARTComponent : public Component, public Stream {
public:
void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
uint32_t get_baud_rate() const { return baud_rate_; }
uint32_t get_config();

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from helpers import git_ls_files, filter_changed
import codecs
import collections
import fnmatch
@ -12,7 +13,6 @@ import functools
import argparse
sys.path.append(os.path.dirname(__file__))
from helpers import git_ls_files, filter_changed
def find_all(a_str, sub):
@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match):
"esphome/components/number/number.h",
"esphome/components/output/binary_output.h",
"esphome/components/output/float_output.h",
"esphome/components/nextion/nextion_base.h",
"esphome/components/sensor/sensor.h",
"esphome/components/stepper/stepper.h",
"esphome/components/switch/switch.h",

View File

@ -1055,10 +1055,6 @@ binary_sensor:
pin: GPIO27
threshold: 1000
id: btn_left
- platform: nextion
page_id: 0
component_id: 2
name: 'Nextion Component 2 Touch'
- platform: template
name: 'Garage Door Open'
id: garage_door
@ -1882,11 +1878,6 @@ display:
intensity: 3
lambda: |-
it.print("1234");
- platform: nextion
uart_id: uart0
lambda: |-
it.set_component_value("gauge", 50);
it.set_component_text("textview", "Hello World!");
- platform: pcd8544
cs_pin: GPIO23
dc_pin: GPIO23

View File

@ -269,6 +269,7 @@ wled:
adalight:
sensor:
- platform: apds9960
type: proximity
@ -534,6 +535,15 @@ sensor:
export_reactive_energy:
name: 'Export Reactive Energy'
- platform: nextion
id: testnumber
name: 'testnumber'
variable_name: testnumber
- platform: nextion
id: testwave
name: 'testwave'
component_id: 2
wave_channel_id: 1
time:
- platform: homeassistant
@ -605,7 +615,14 @@ binary_sensor:
binary_sensors:
- id: custom_binary_sensor
name: Custom Binary Sensor
- platform: nextion
page_id: 0
component_id: 2
name: 'Nextion Component 2 Touch'
- platform: nextion
id: r0_sensor
name: 'R0 Sensor'
component_name: page0.r0
globals:
- id: my_global_string
type: std::string
@ -653,6 +670,11 @@ text_sensor:
text_sensors:
- id: custom_text_sensor
name: Custom Text Sensor
- platform: nextion
name: text0
id: text0
update_interval: 4s
component_name: text0
script:
- id: my_script
@ -704,6 +726,10 @@ switch:
switches:
- id: custom_switch
name: Custom Switch
- platform: nextion
id: r0
name: 'R0 Switch'
component_name: page0.r0
custom_component:
lambda: |-
@ -1086,6 +1112,16 @@ display:
id: my_matrix
lambda: |-
it.printdigit("hello");
- platform: nextion
uart_id: uart1
tft_url: 'http://esphome.io/default35.tft'
update_interval: 5s
on_sleep:
then:
lambda: 'ESP_LOGD("display","Display went to sleep");'
on_wake:
then:
lambda: 'ESP_LOGD("display","Display woke up");'
http_request:
useragent: esphome/device