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 Jesse Hills
parent f30b8f6b3c
commit 4dbf1c521e
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
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)

File diff suppressed because it is too large Load diff

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