#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::deque cmd_queue; unsigned long last_set_cmd_timestamp; std::map sourcelist; std::vector pwr_off_query_cmds{ "PWR?", "LAMP?" }; std::vector pwr_on_query_cmds{ // Projection screen adjustment settings /* "VKEYSTONE?", "HKEYSTONE?", "QC?", "CORRECTMET?", "ASPECT?", "LUMINANCE?", "OVSCAN?", */ // // Source/Input/Resolution settings // "SOURCE?", // TODO: implement parser // 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?", // TODO: implement parser // Environment settings /* "HREVERSE?", "VREVERSE?", "MSEL?", "SPEED?", "ILLUM?", "STANDBYCONF?", */ // "PRODUCT?", // // Home Screen settings // "AUTOHOME?", // TODO: implement parser // Network settings // "WLPWR?", // TODO: implement parser // Bluetooth "BTAUDIO?", // TODO: implement parser // Information "SIGNAL?", // "SOURCELIST?" // Pushed on demand if sensor value is empty (e.g. after boot-up) // "SOURCELISTA?", // same as SOURCELIST // "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"); }; void tw7100Component::update() { static const char *const TAG = "update()"; if (cmd_queue.empty()) { for (auto &cmd : pwr_off_query_cmds) { cmd_queue.push_back(cmd); } if ((powered_->state)) { if (this->serial_->state.length() < 1) { cmd_queue.push_back("SNO?"); } 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)) { 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 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) { 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); } else if (cmd == "PWR" && param == "ON") { cmd_queue.push_front(cmd + " " + param); } } void tw7100Component::update_sensor(std::pair 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); } else if (cmd == "SOURCELIST") { 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); ESP_LOGV(TAG, "first_token_end: %i, second_token_end: %i, unparsed_result.length(): %li", first_token_end, second_token_end, unparsed_result.length() ); 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 } 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); } else { ESP_LOGW(TAG, "Command %s unknown, skipping updating sensor with value", cmd.c_str()); } } std::pair 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