Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Home Assistant API instead of MQTT #34

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 87 additions & 27 deletions components/nspanel_lovelace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,103 @@
import esphome.config_validation as cv
import esphome.codegen as cg

from esphome.components import mqtt, uart
from esphome.components import mqtt, uart, api
from esphome.const import (
CONF_ID,
CONF_TRIGGER_ID,
)

AUTO_LOAD = ["text_sensor"]
CODEOWNERS = ["@sairon"]
DEPENDENCIES = ["mqtt", "uart", "wifi", "esp32"]
DEPENDENCIES = [
"uart",
"wifi",
"esp32",
] # adding dependency later - depends on at least on of "mqtt" or "api"


nspanel_lovelace_ns = cg.esphome_ns.namespace("nspanel_lovelace")
NSPanelLovelace = nspanel_lovelace_ns.class_("NSPanelLovelace", cg.Component, uart.UARTDevice)
NSPanelLovelace = nspanel_lovelace_ns.class_(
"NSPanelLovelace", cg.Component, uart.UARTDevice
)


NSPanelLovelaceMsgIncomingTrigger = nspanel_lovelace_ns.class_(
"NSPanelLovelaceMsgIncomingTrigger",
automation.Trigger.template(cg.std_string)
NSPanelLovelaceMessageFromNextionTrigger = nspanel_lovelace_ns.class_(
"NSPanelLovelaceMessageFromNextionTrigger",
automation.Trigger.template(cg.std_string),
)
NSPanelLovelaceMessageToNextionTrigger = nspanel_lovelace_ns.class_(
"NSPanelLovelaceMessageToNextionTrigger", automation.Trigger.template(cg.std_string)
)

CONF_USE_API = "use_api"
CONF_API_PARENT_ID = "api_parent_id"
CONF_MQTT_PARENT_ID = "mqtt_parent_id"
CONF_MQTT_RECV_TOPIC = "mqtt_recv_topic"
CONF_MQTT_SEND_TOPIC = "mqtt_send_topic"
CONF_INCOMING_MSG = "on_incoming_msg"
CONF_MESSAGE_FROM_NEXTION = "on_message_from_nextion"
CONF_MESSAGE_TO_NEXTION = "on_message_to_nextion"
CONF_BERRY_DRIVER_VERSION = "berry_driver_version"
CONF_USE_MISSED_UPDATES_WORKAROUND = "use_missed_updates_workaround"
CONF_UPDATE_BAUD_RATE = "update_baud_rate"


def validate_config(config):
if int(config[CONF_BERRY_DRIVER_VERSION]) > 0:
if "CustomSend" not in config[CONF_MQTT_SEND_TOPIC]:
# backend uses topic_send.replace("CustomSend", ...) for GetDriverVersion and FlashNextion
raise cv.Invalid(f"{CONF_MQTT_SEND_TOPIC} must contain \"CustomSend\" for correct backend compatibility.\n"
f"Either change it or set {CONF_BERRY_DRIVER_VERSION} to 0.")
# add dependency - depends either "mqtt" or "api"
if not CONF_API_PARENT_ID in config and not CONF_MQTT_PARENT_ID in config:
raise cv.Invalid("Requires at least one of mqtt or api components.")

if config[CONF_USE_API]:
if not CONF_API_PARENT_ID in config:
raise cv.Invalid("This setting of 'use_api: True' requires api component.")
else:
if not CONF_MQTT_PARENT_ID in config:
raise cv.Invalid(
"This setting of 'use_api: False' requires mqtt component."
)

if CONF_MQTT_PARENT_ID in config:
if int(config[CONF_BERRY_DRIVER_VERSION]) > 0:
if "CustomSend" not in config[CONF_MQTT_SEND_TOPIC]:
# backend uses topic_send.replace("CustomSend", ...) for GetDriverVersion and FlashNextion
raise cv.Invalid(
f'{CONF_MQTT_SEND_TOPIC} must contain "CustomSend" for correct backend compatibility.\n'
f"Either change it or set {CONF_BERRY_DRIVER_VERSION} to 0."
)

return config


CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(NSPanelLovelace),
cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent),
cv.Optional(CONF_MQTT_RECV_TOPIC, default="tele/nspanel/RESULT"): cv.string,
cv.Optional(CONF_MQTT_SEND_TOPIC, default="cmnd/nspanel/CustomSend"): cv.string,
cv.Optional(CONF_INCOMING_MSG): automation.validate_automation(
cv.OnlyWith(CONF_API_PARENT_ID, "api"): cv.use_id(api.APIServer),
cv.OnlyWith(CONF_USE_API, "api", default=False): cv.boolean,
cv.OnlyWith(CONF_MQTT_PARENT_ID, "mqtt"): cv.use_id(
mqtt.MQTTClientComponent
),
cv.OnlyWith(
CONF_MQTT_RECV_TOPIC, "mqtt", default="tele/nspanel/RESULT"
): cv.All(cv.requires_component("mqtt"), cv.string),
cv.OnlyWith(
CONF_MQTT_SEND_TOPIC, "mqtt", default="cmnd/nspanel/CustomSend"
): cv.All(cv.requires_component("mqtt"), cv.string),
cv.Optional(CONF_MESSAGE_FROM_NEXTION): automation.validate_automation(
cv.Schema(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
NSPanelLovelaceMessageFromNextionTrigger
),
}
)
),
cv.Optional(CONF_MESSAGE_TO_NEXTION): automation.validate_automation(
cv.Schema(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NSPanelLovelaceMsgIncomingTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
NSPanelLovelaceMessageToNextionTrigger
),
}
)
),
Expand All @@ -58,30 +107,41 @@ def validate_config(config):
cv.Optional(CONF_UPDATE_BAUD_RATE, default=921600): cv.positive_int,
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
validate_config
)
validate_config,
)


async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

mqtt_parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID])
cg.add(var.set_mqtt(mqtt_parent))
cg.add(var.set_recv_topic(config[CONF_MQTT_RECV_TOPIC]))
cg.add(var.set_send_topic(config[CONF_MQTT_SEND_TOPIC]))
if CONF_MQTT_PARENT_ID in config:
mqtt_parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID])
cg.add(var.set_mqtt(mqtt_parent))
cg.add(var.set_recv_topic(config[CONF_MQTT_RECV_TOPIC]))
cg.add(var.set_send_topic(config[CONF_MQTT_SEND_TOPIC]))

cg.add(var.set_berry_driver_version(config[CONF_BERRY_DRIVER_VERSION]))
cg.add(var.set_missed_updates_workaround(config[CONF_USE_MISSED_UPDATES_WORKAROUND]))
cg.add(
var.set_missed_updates_workaround(config[CONF_USE_MISSED_UPDATES_WORKAROUND])
)
cg.add(var.set_update_baud_rate(config[CONF_UPDATE_BAUD_RATE]))
cg.add(var.set_use_api(config[CONF_USE_API]))

for conf in config.get(CONF_INCOMING_MSG, []):
for conf in config.get(CONF_MESSAGE_FROM_NEXTION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)

for conf in config.get(CONF_MESSAGE_TO_NEXTION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.std_string.operator("ref"), "x")], conf
)

cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None)
cg.add_define("USE_NSPANEL_LOVELACE")
13 changes: 10 additions & 3 deletions components/nspanel_lovelace/automation.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
namespace esphome {
namespace nspanel_lovelace {

class NSPanelLovelaceMsgIncomingTrigger : public Trigger<std::string> {
class NSPanelLovelaceMessageFromNextionTrigger : public Trigger<std::string> {
public:
explicit NSPanelLovelaceMsgIncomingTrigger(NSPanelLovelace *parent) {
parent->add_incoming_msg_callback([this](const std::string &value) { this->trigger(value); });
explicit NSPanelLovelaceMessageFromNextionTrigger(NSPanelLovelace *parent) {
parent->add_message_from_nextion_callback([this](const std::string &value) { this->trigger(value); });
}
};

class NSPanelLovelaceMessageToNextionTrigger : public Trigger<std::string&> {
public:
explicit NSPanelLovelaceMessageToNextionTrigger(NSPanelLovelace *parent) {
parent->add_message_to_nextion_callback([this](std::string &value) { this->trigger(value); });
}
};

Expand Down
142 changes: 109 additions & 33 deletions components/nspanel_lovelace/nspanel_lovelace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,109 @@
#include "esphome/core/helpers.h"
#include "esphome/core/util.h"

#ifdef USE_API
#include "esphome/core/base_automation.h"
#endif

namespace esphome {
namespace nspanel_lovelace {

static const char *const TAG = "nspanel_lovelace";

#ifdef USE_API
static const char *const HA_API_EVENT = "esphome.nspanel.data";
#endif

void NSPanelLovelace::setup() {
this->mqtt_->subscribe(this->send_topic_, [this](const std::string &topic, const std::string &payload) {
this->send_custom_command(payload);

if (!this->use_api_) {
#ifdef USE_MQTT
this->mqtt_->subscribe(this->send_topic_, [this](const std::string &topic, const std::string &payload)
{ this->app_custom_send(payload); });

if (this->berry_driver_version_ > 0)
{
this->mqtt_->subscribe(std::regex_replace(this->send_topic_, std::regex("CustomSend"), "GetDriverVersion"),
[this](const std::string &topic, const std::string &payload)
{
this->app_get_driver_version();
});

this->mqtt_->subscribe(std::regex_replace(this->send_topic_, std::regex("CustomSend"), "FlashNextion"),
[this](const std::string &topic, const std::string &payload)
{
this->app_flash_nextion(payload);
});
}
#endif
}
else {
#ifdef USE_API
auto api_userservicetrigger = new api::UserServiceTrigger<int32_t, std::string>("nspanelui_api_call", {"command", "data"});
api::global_api_server->register_user_service(api_userservicetrigger);
auto automation = new Automation<int32_t, std::string>(api_userservicetrigger);
auto lambdaaction = new LambdaAction<int32_t, std::string>([=](int32_t command, std::string data) -> void {
switch (command) {
case 1: // GetDriverVersion
this->app_get_driver_version();
break;
case 2: // CustomSend
this->app_custom_send(data);
break;
case 255: // FlashNextionTft
this->app_flash_nextion(data);
break;
} });
automation->add_actions({lambdaaction});
#endif
}
}

void NSPanelLovelace::app_custom_send(const std::string &payload) {

// call message trigger
std::string payload1 = payload;
this->message_to_nextion_callback_.call(payload1);

// trigger/s may modify the incoming message - if they emoty the message, do not send it
if (!payload1.empty()) {
this->send_custom_command(payload1);
// workaround for https://github.com/sairon/esphome-nspanel-lovelace-ui/issues/8
if (this->use_missed_updates_workaround_) delay(75);
});

if (this->berry_driver_version_ > 0) {
this->mqtt_->subscribe(std::regex_replace(this->send_topic_, std::regex("CustomSend"), "GetDriverVersion"),
[this](const std::string &topic, const std::string &payload) {
this->mqtt_->publish_json(this->recv_topic_, [this](ArduinoJson::JsonObject root) {
root["nlui_driver_version"] = this->berry_driver_version_;
});
});

this->mqtt_->subscribe(std::regex_replace(this->send_topic_, std::regex("CustomSend"), "FlashNextion"),
[this](const std::string &topic, const std::string &payload) {
ESP_LOGD(TAG, "FlashNextion called with URL '%s'", payload.c_str());

// Calling upload_tft in MQTT callback directly would crash ESPHome - using a scheduler
// task avoids that. Maybe there is another way?
App.scheduler.set_timeout(
this, "nspanel_lovelace_flashnextion_upload", 100, [this, payload]() {
ESP_LOGD(TAG, "Starting FlashNextion with URL '%s'", payload.c_str());
this->upload_tft(payload);
});
});
}
}

int NSPanelLovelace::app_get_driver_version() {
if (!this->use_api_) {
#ifdef USE_MQTT
this->mqtt_->publish_json(this->recv_topic_, [this](ArduinoJson::JsonObject root)
{ root["nlui_driver_version"] = this->berry_driver_version_; });
#endif
}
else {
#ifdef USE_API
std::string message = std::to_string(this->berry_driver_version_);
this->fire_homeassistant_event(HA_API_EVENT, {{"nlui_driver_version", message}});
#endif
}

return this->berry_driver_version_;
}

void NSPanelLovelace::app_flash_nextion(const std::string &payload) {
ESP_LOGD(TAG, "FlashNextion called with URL '%s'", payload.c_str());

// Calling upload_tft in MQTT callback directly would crash ESPHome - using a scheduler
// task avoids that. Maybe there is another way?
App.scheduler.set_timeout(
this, "nspanel_lovelace_flashnextion_upload", 100, [this, payload]() {
ESP_LOGD(TAG, "Starting FlashNextion with URL '%s'", payload.c_str());
//!! need to add -- id(ble_tracker).stop_scan();
this->upload_tft(payload); });
}

void NSPanelLovelace::loop() {
// don't interfere with update or reparse mode
if (this->is_updating_ || this->reparse_mode_) {
return;
}
Expand Down Expand Up @@ -97,20 +164,29 @@ bool NSPanelLovelace::process_data_() {
const uint8_t *message_data = data + 4;
std::string message(message_data, message_data + length);

this->process_command_(message);
this->process_command_from_nextion(message);
this->buffer_.clear();
return true;
}

void NSPanelLovelace::process_command_(const std::string &message) {
this->mqtt_->publish_json(this->recv_topic_, [message](ArduinoJson::JsonObject root){
root["CustomRecv"] = message;
});
this->incoming_msg_callback_.call(message);
}
void NSPanelLovelace::process_command_from_nextion(const std::string &message)
{
if (this->is_updating_) return; // don't interfere with update

if (!this->use_api_) {
#ifdef USE_MQTT
this->mqtt_->publish_json(this->recv_topic_, [message](ArduinoJson::JsonObject root)
{ root["CustomRecv"] = message; });
#endif
}
else {
#ifdef USE_API
this->fire_homeassistant_event(HA_API_EVENT, {{"CustomRecv", message}});
#endif
}

void NSPanelLovelace::add_incoming_msg_callback(std::function<void(std::string)> callback) {
this->incoming_msg_callback_.add(std::move(callback));
// call message trigger
this->message_from_nextion_callback_.call(message);
}

void NSPanelLovelace::dump_config() { ESP_LOGCONFIG(TAG, "NSPanelLovelace:"); }
Expand Down
Loading