esphome-tw7100/tw7100.cpp

291 lines
8.6 KiB
C++
Raw Normal View History

#include "tw7100.h"
2024-09-17 00:58:39 +02:00
#include "select/tw7100_select.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;
2024-09-15 20:46:19 +02:00
std::deque<std::string> cmd_queue;
unsigned long last_set_cmd_timestamp;
2024-09-15 22:38:38 +02:00
std::map<int, std::string> sourcelist;
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?",
2024-09-17 00:58:39 +02:00
*/
"LUMINANCE?",
2024-09-17 00:58:39 +02:00
/*
"OVSCAN?",
*/
//
// Source/Input/Resolution settings
2024-09-16 22:38:48 +02:00
"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?",
*/
//
2024-09-15 20:08:51 +02:00
// "MUTE?", // TODO: implement parser
// Environment settings
/*
"HREVERSE?",
"VREVERSE?",
"MSEL?",
"SPEED?",
"ILLUM?",
"STANDBYCONF?",
*/
// "PRODUCT?",
//
// Home Screen settings
2024-09-15 20:08:51 +02:00
// "AUTOHOME?", // TODO: implement parser
// Network settings
2024-09-15 20:08:51 +02:00
// "WLPWR?", // TODO: implement parser
// Bluetooth
"BTAUDIO?", // TODO: implement parser
// Information
"SIGNAL?",
2024-09-15 22:38:38 +02:00
// "SOURCELIST?" // Pushed on demand if sensor value is empty (e.g. after boot-up)
// "SOURCELISTA?", // same as SOURCELIST
2024-09-15 20:08:51 +02:00
// "LOGTO?", // TODO: implement parser
// "SNO?" // Pushed on demand if sensor value is empty (e.g. after boot-up)
};
void tw7100Component::setup() {
static const char *const TAG = "setup()";
ESP_LOGV(TAG, "SETUP");
2024-09-16 22:38:48 +02:00
source_select_->setup();
2024-09-17 00:58:39 +02:00
luminance_select_->setup();
};
void tw7100Component::update() {
static const char *const TAG = "update()";
2024-09-15 20:46:19 +02:00
if (cmd_queue.empty()) {
for (auto &cmd : pwr_off_query_cmds) {
cmd_queue.push_back(cmd);
}
2024-09-15 20:08:51 +02:00
if ((powered_->state)) {
2024-09-15 20:25:06 +02:00
if (this->serial_->state.length() < 1) {
cmd_queue.push_back("SNO?");
}
2024-09-15 22:38:38 +02:00
if (sourcelist.empty()) {
cmd_queue.push_back("SOURCELIST?");
}
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.empty() && !waiting_for_answer && (millis() - last_set_cmd_timestamp > _timeout_after_cmd)) {
2024-09-15 20:46:19 +02:00
std::string cmd = cmd_queue.front();
cmd_queue.pop_front();
ESP_LOGV(TAG, "sending out command: %s", cmd.c_str());
write_str((cmd + "\r\n").c_str());
flush();
if (cmd.find("?") != std::string::npos) { // only wait for response on CMD? requests
waiting_for_answer = true;
} else {
last_set_cmd_timestamp = millis();
}
}
} 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::push_cmd(std::string cmd, std::string param) {
2024-09-15 18:44:45 +02:00
static const char *const TAG = "push_cmd()";
if (powered_->state) {
ESP_LOGV(TAG, "pushing priority cmd (%s) from external component", cmd.c_str());
cmd_queue.push_front(cmd + "?");
cmd_queue.push_front(cmd + " " + param);
2024-09-16 21:47:03 +02:00
} else if (cmd == "PWR" && param == "ON") {
cmd_queue.push_front(cmd + " " + param);
}
2024-09-15 18:44:45 +02:00
}
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_switch_->publish_state(value > 0); // ack previous cmd or update switch
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);
2024-09-17 00:58:39 +02:00
auto call = luminance_select_->make_call();
call.set_option(luminance_select_->luminance[value_string]);
call.perform();
} else if (cmd == "SOURCELIST") {
2024-09-15 22:38:38 +02:00
std::string unparsed_result = value_string;
int first_token_end;
int second_token_end;
uint8_t parse_rounds = 0; // safety fallback if data contains unexpected content
do {
first_token_end = unparsed_result.find(" ");
second_token_end = unparsed_result.find(" ", first_token_end+1);
2024-09-16 21:47:34 +02:00
ESP_LOGV(TAG, "first_token_end: %i, second_token_end: %i, unparsed_result.length(): %li", first_token_end, second_token_end, unparsed_result.length() );
2024-09-15 22:38:38 +02:00
std::string key_string = unparsed_result.substr(0, first_token_end);
std::string value = unparsed_result.substr(first_token_end+1, second_token_end-first_token_end-1);
int key = std::stoi(key_string, nullptr, 16);
sourcelist[key] = value;
ESP_LOGV(TAG, "Added %s with key %i(0x%02x) to sourcelist", value.c_str(), key, key);
unparsed_result.erase(0, second_token_end+1);
ESP_LOGV(TAG, "rest string: %s", unparsed_result.c_str());
parse_rounds+=1;
} while (second_token_end > 0 && parse_rounds <= 50);
// TODO: if we exceed parse_rounds we most likely encountered a critical issue, inform user somehow
2024-09-15 20:25:06 +02:00
} else if (cmd == "SNO") {
serial_->publish_state(value_string);
} else if (cmd == "BTAUDIO") {
ESP_LOGV(TAG, "updating btaudio switch");
int value = std::stoi(value_string);
btaudio_switch_->publish_state(value > 0);
2024-09-16 22:38:48 +02:00
} else if (cmd == "SOURCE") {
ESP_LOGV(TAG, "updating source select");
auto call = source_select_->make_call();
2024-09-17 00:58:39 +02:00
call.set_option(source_select_->sources[value_string]);
2024-09-16 22:38:48 +02:00
call.perform();
} 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