Add tw7100 component with config generator

This commit is contained in:
sqozz 2024-09-15 16:43:29 +02:00
parent 4bf101d938
commit 6eced6cc0d
6 changed files with 432 additions and 0 deletions

26
__init__.py Normal file
View file

@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
CODEOWNERS = ["@sqozz"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"]
tw7100_ns = cg.esphome_ns.namespace("tw7100")
tw7100 = tw7100_ns.class_("tw7100Component", cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(tw7100),
}).extend(cv.polling_component_schema("60s")).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"tw7100", baud_rate=9600, require_rx=True, require_tx=True
)
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)

38
binary_sensor.py Normal file
View file

@ -0,0 +1,38 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_CONNECTIVITY,
CONF_POWER,
CONF_ID,
)
from . import tw7100
DEPENDENCIES = ["tw7100"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(tw7100),
cv.Optional("signal_detected"): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_CONNECTIVITY
),
cv.Optional(CONF_POWER): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_POWER
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if "signal_detected" in config:
sens = await binary_sensor.new_binary_sensor(config["signal_detected"])
cg.add(parent.set_has_signal(sens))
if CONF_POWER in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_POWER])
cg.add(parent.set_powered(sens))

33
example.yaml Normal file
View file

@ -0,0 +1,33 @@
external_components:
- source:
type: local
path: external_components
components:
- tw7100
tw7100:
uart_id: uart_bus
uart:
id: uart_bus
tx_pin: 15
rx_pin: 13
baud_rate: 9600
binary_sensor:
- platform: tw7100
signal_detected:
name: "Signal"
power:
name: "Powered"
sensor:
- platform: tw7100
hours:
name: "Projector lamp hours"
power:
name: "Projector power status"
state:
name: "Projector signal status"
illuminance:
name: "Projector luminance level"

58
sensor.py Normal file
View file

@ -0,0 +1,58 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_ILLUMINANCE,
CONF_ILLUMINANCE,
CONF_HOURS,
CONF_STATE,
CONF_POWER,
CONF_ID,
)
from . import tw7100
DEPENDENCIES = ["tw7100"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(tw7100),
cv.Optional(CONF_HOURS): sensor.sensor_schema(
device_class=DEVICE_CLASS_DURATION # Lamp hours
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER # Power status
),
cv.Optional(CONF_STATE): sensor.sensor_schema(
device_class=DEVICE_CLASS_POWER # Signal status
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_ILLUMINANCE # Luminance level
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_HOURS in config:
sens = await sensor.new_sensor(config[CONF_HOURS])
cg.add(parent.set_lamp_hours(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(parent.set_power_status(sens))
if CONF_STATE in config:
sens = await sensor.new_sensor(config[CONF_STATE])
cg.add(parent.set_signal_status(sens))
if CONF_ILLUMINANCE in config:
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(parent.set_luminance_level(sens))

228
tw7100.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "tw7100.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace tw7100 {
bool waiting_for_answer = false;
std::vector<std::string> cmd_queue;
std::vector<std::string> pwr_off_query_cmds{ "PWR?", "LAMP?" };
std::vector<std::string> pwr_on_query_cmds{
// Projection screen adjustment settings
/*
"VKEYSTONE?",
"HKEYSTONE?",
"QC?",
"CORRECTMET?",
"ASPECT?",
"LUMINANCE?",
"OVSCAN?",
*/
//
// Source/Input/Resolution settings
"SOURCE?",
// Image settings
/*
"BRIGHT?",
"CONTRAST?",
"DENSITY?",
"TINT?",
"CTEMP?",
"FCOLOR?",
"CMODE?",
"NRS?",
"MPEGNRS?",
"OFFSETR?",
"OFFSETG?",
"OFFSETB?",
"GAINR?",
"GAING?",
"GAINB?",
"GAMMA?",
"CSEL?",
"4KENHANCE?",
"IMGPRESET?",
"SHRF?",
"SHRS?",
"DERANGE?",
"MCFI?",
"CLRSPACE?",
"DYNRANGE?",
"HDRPQ?",
"HDRHLG?",
"IMGPROC?",
*/
//
// Sound settings
/*
"VOL?",
"AUDIOOUT?",
*/
//
"MUTE?",
// Environment settings
/*
"HREVERSE?",
"VREVERSE?",
"MSEL?",
"SPEED?",
"ILLUM?",
"STANDBYCONF?",
"PRODUCT?",
*/
//
// Home Screen settings
"AUTOHOME?",
// Network settings
"WLPWR?",
// Bluetooth
"BTAUDIO?",
// Information
"SIGNAL?",
"SOURCELIST?",
// "SOURCELISTA?", // same as SOURCELIST
"LOGTO?",
"SNO?"
};
void tw7100Component::setup() {
static const char *const TAG = "setup()";
ESP_LOGV(TAG, "SETUP");
};
void tw7100Component::update() {
static const char *const TAG = "update()";
if (cmd_queue.size() == 0) {
for (auto &cmd : pwr_off_query_cmds) {
cmd_queue.push_back(cmd);
}
if ((powered_->state)) {;
for (auto &cmd : pwr_on_query_cmds) {
cmd_queue.push_back(cmd);
}
}
}
};
void tw7100Component::loop() {
static const char *const TAG = "loop()";
unsigned long _startMillis; // used for timeout measurement
_startMillis = millis();
do {
std::string response = readStringUntil(':');
if (response == "") { // no response on bus, we can use our time to do something else
if (cmd_queue.size() > 0 && !waiting_for_answer) {
waiting_for_answer = true;
std::string cmd = cmd_queue[0];
cmd_queue.erase(cmd_queue.begin());
ESP_LOGV(TAG, "sending out command: %s", cmd.c_str());
write_str((cmd + "\r\n").c_str());
flush();
}
} else { // response found, handle eventual errors
ESP_LOGV(TAG, "buffer content: %s", response.c_str());
std::pair<std::string, std::string> parsed_response = parse_response(response);
if (parsed_response.first == "ERR" && parsed_response.second == "ERR") {
if (powered_->state) {;
cmd_queue.push_back("ERR?"); // TODO: produces a ERR? loop on bootup if projector is turned off
} else {
ESP_LOGE(TAG, "got ERR while projector is off, check if PWR+LAMP is too much?");
}
} else if (parsed_response.first == "ERR") {
// TODO: Handle error
} else {
update_sensor(parsed_response);
}
waiting_for_answer = false;
}
//ESP_LOGV(TAG, "read processing finished");
} while (millis() - _startMillis < _max_loop_time);
//ESP_LOGV(TAG, "inner timed loop done");
};
void tw7100Component::dump_config() {
static const char *const TAG = "dump_config()";
ESP_LOGCONFIG(TAG, "TW7100:");
this->check_uart_settings(9600);
};
void tw7100Component::update_sensor(std::pair<std::string, std::string> data) {
static const char *const TAG = "update_sensor()";
std::string cmd = data.first;
std::string value_string = data.second;
if (cmd == "PWR") {
ESP_LOGV(TAG, "updating power sensors");
int value = std::stoi(value_string);
powered_->publish_state(value > 0);
power_status_->publish_state(value);
} else if (cmd == "LAMP") {
ESP_LOGV(TAG, "updating lamp sensors");
int value = std::stoi(value_string);
lamp_hours_->publish_state(value);
} else if (cmd == "SIGNAL") {
ESP_LOGV(TAG, "updating signal sensors");
int value = std::stoi(value_string);
has_signal_->publish_state(value > 0);
signal_status_->publish_state(value);
} else if (cmd == "LUMINANCE") {
ESP_LOGV(TAG, "updating luminance sensors");
int value = std::stoi(value_string);
luminance_level_->publish_state(value);
} else if (cmd == "SOURCELIST") {
for(char& c : value_string) {
ESP_LOGV(TAG, "%c (%02x)", c, c);
}
} else {
ESP_LOGW(TAG, "Command %s unknown, skipping updating sensor with value", cmd.c_str());
}
}
std::pair<std::string, std::string> tw7100Component::parse_response(std::string data) {
static const char *const TAG = "parse_response()";
std::string key = "";
std::string value = "";
data.erase(std::remove(data.begin(), data.end(), '\r'), data.cend());
data.erase(std::remove(data.begin(), data.end(), '\n'), data.cend());
if (data != "") {
key = data.substr(0, data.find("="));
value = data.substr(data.find("=") + 1, data.length());
}
ESP_LOGV(TAG, "parsed response into (key: %s, value: %s)", key.c_str(), value.c_str());
return std::make_pair(key, value);
}
int tw7100Component::timedRead() {
unsigned long _startMillis; // used for timeout measurement
int c;
_startMillis = millis();
do {
if (this->available() > 0) {
c = this->read();
//ESP_LOGV("timedRead()", "timed read byte: %c (%x)", c, c);
if (c >= 0) {
return c;
}
}
} while (millis() - _startMillis < _readStringUntil_timeout);
return -1; // -1 indicates timeout
}
std::string tw7100Component::readStringUntil(char terminator) {
std::string line="";
int c = timedRead();
while (c >= 0 && (char)c != terminator) {
line += (char)c;
c = timedRead();
}
return line;
};
} // namespace tw7100
} // namespace esphome

49
tw7100.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace tw7100 {
class tw7100Component : public PollingComponent, public uart::UARTDevice {
private:
unsigned long _max_loop_time = 30;
unsigned long _readStringUntil_timeout = _max_loop_time/5; // number of milliseconds to wait for the next char before aborting timed read
public:
tw7100Component() = default;
std::string readStringUntil(char terminator);
void update_sensor(std::pair<std::string, std::string> data);
std::pair<std::string, std::string> parse_response(std::string data);
int timedRead(void);
void setup() override;
void update() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_powered(binary_sensor::BinarySensor *powered) { this->powered_ = powered; }
void set_has_signal(binary_sensor::BinarySensor *has_signal) { this->has_signal_ = has_signal; }
void set_lamp_hours(sensor::Sensor *lamp_hours) { this->lamp_hours_ = lamp_hours; }
void set_power_status(sensor::Sensor *power_status) { this->power_status_ = power_status; }
void set_signal_status(sensor::Sensor *signal_status) { this->signal_status_ = signal_status; }
void set_luminance_level(sensor::Sensor *luminance_level) { this->luminance_level_ = luminance_level; }
void set_serial(text_sensor::TextSensor *serial) { this->serial_ = serial; }
protected:
binary_sensor::BinarySensor *powered_{nullptr};
binary_sensor::BinarySensor *has_signal_{nullptr};
sensor::Sensor *lamp_hours_{nullptr};
sensor::Sensor *power_status_{nullptr};
sensor::Sensor *signal_status_{nullptr};
sensor::Sensor *luminance_level_{nullptr};
text_sensor::TextSensor *serial_{nullptr};
};
} // namespace tw7100
} // namespace esphome