#include "tw7100.h" #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; bool waiting_for_error_report = false; unsigned long waiting_for_answer_since = 0; 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?", "ASPECT?", "LUMINANCE?", "OVSCAN?", /* "QC?", "CORRECTMET?", */ // 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?", "DESTRENGTH?", "MCFI?", "CLRSPACE?", "DYNRANGE?", "HDRPQ?", "HDRHLG?", "IMGPROC?", // Sound settings "VOL?", "AUDIOOUT?", "MUTE?", // Environment settings "HREVERSE?", "VREVERSE?", "MSEL?", "SPEED?", "ILLUM?", "STANDBYCONF?", // "PRODUCT?", // produces ERR // Home Screen settings "AUTOHOME?", // Network settings "WLPWR?", // Bluetooth "BTAUDIO?", // Information "SIGNAL?", "LOGTO?", // produces ERR // "SNO?" // Pushed on demand if sensor value is empty (e.g. after boot-up) // "SOURCELIST?" // Parser present in git history but removed until esphome can update selects after api connections are established // "SOURCELISTA?", // same as SOURCELIST }; void tw7100Component::setup() { static const char *const TAG = "setup()"; ESP_LOGV(TAG, "SETUP"); if (source_select_ != nullptr) source_select_->setup(); if (luminance_select_ != nullptr) luminance_select_->setup(); if (aspect_select_ != nullptr) aspect_select_->setup(); if (cmode_select_ != nullptr) cmode_select_->setup(); if (gamma_select_ != nullptr) gamma_select_->setup(); if (imgpreset_select_ != nullptr) imgpreset_select_->setup(); if (mcfi_select_ != nullptr) mcfi_select_->setup(); if (ovscan_select_ != nullptr) ovscan_select_->setup(); if (clrspace_select_ != nullptr) clrspace_select_->setup(); if (dynrange_select_ != nullptr) dynrange_select_->setup(); if (msel_select_ != nullptr) msel_select_->setup(); if (speed_select_ != nullptr) speed_select_->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?"); } 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 > std::max(_timeout_after_cmd, _specialTimeoutMillis))) { 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 _specialTimeoutMillis = 0; waiting_for_answer = true; waiting_for_answer_since = millis(); } else { last_set_cmd_timestamp = millis(); } } if (waiting_for_answer && (millis() - waiting_for_answer_since > 1000)) { ESP_LOGE(TAG, "no response for last request since 1000ms, timing out!"); waiting_for_answer = false; } } 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 && !waiting_for_error_report) { ESP_LOGW(TAG, "found ERR response, pushing request to fetch full error"); waiting_for_error_report = true; cmd_queue.push_front("ERR?"); } else { ESP_LOGW(TAG, "got ERR while projector is off or waiting for a previous error report"); } } else if (parsed_response.first == "ERR") { waiting_for_error_report = false; ESP_LOGE(TAG, "found ERR report, reason is: %s", parsed_response.second.c_str()); // 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) { if (cmd == "PWR" && param == "OFF") { _specialTimeoutMillis = 5000; // time to wait after PWR OFF until projector responds again cmd_queue.clear(); // clear eventual existing query commands to avoid errors } if ((cmd == "SOURCE")||(cmd == "MUTE")) { _specialTimeoutMillis = 4000; // time to wait after source change or AV mute } 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") { _specialTimeoutMillis = 30000; // time to wait after PWR ON until projector responds again cmd_queue.clear(); // clear eventual existing query commands to avoid errors cmd_queue.push_front(cmd + "?"); 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); auto call = luminance_select_->make_call(); call.set_option(luminance_select_->luminance[value_string]); call.perform(); } else if (cmd == "SOURCE") { ESP_LOGV(TAG, "updating source select"); auto call = source_select_->make_call(); call.set_option(source_select_->sources[value_string]); call.perform(); } else if (cmd == "ASPECT") { ESP_LOGV(TAG, "updating aspect select"); if(aspect_select_->aspect.find(value_string) != aspect_select_->aspect.end()) { auto call = aspect_select_->make_call(); call.set_option(aspect_select_->aspect[value_string]); call.perform(); } else { ESP_LOGE(TAG, "Result %s not present in options", value_string); } } else if (cmd == "OVSCAN") { ESP_LOGV(TAG, "updating ovscan select"); } else if (cmd == "CMODE") { ESP_LOGV(TAG, "updating cmode select"); } else if (cmd == "GAMMA") { ESP_LOGV(TAG, "updating gamma select"); } else if (cmd == "IMGPRESET") { ESP_LOGV(TAG, "updating imgpreset select"); } else if (cmd == "MCFI") { ESP_LOGV(TAG, "updating mcfi select"); } else if (cmd == "CLRSPACE") { ESP_LOGV(TAG, "updating clrspace select"); } else if (cmd == "DYNRANGE") { ESP_LOGV(TAG, "updating dynrange select"); } else if (cmd == "MSEL") { ESP_LOGV(TAG, "updating msel select"); } else if (cmd == "SPEED") { ESP_LOGV(TAG, "updating speed select"); } 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 if (cmd == "4KENHANCE") { ESP_LOGV(TAG, "updating 4kenhance switch"); int value = std::stoi(value_string); _4kenhance_switch_->publish_state(value > 0); } else if (cmd == "IMGPROC") { ESP_LOGV(TAG, "updating imgproc switch"); int value = std::stoi(value_string); img_processing_switch_->publish_state(value > 1); } else if (cmd == "AUDIOOUT") { ESP_LOGV(TAG, "updating audioout switch"); int value = std::stoi(value_string); audioout_switch_->publish_state(value == 11); } else if (cmd == "HREVERSE") { ESP_LOGV(TAG, "updating hflip switch"); hflip_switch_->publish_state(value_string == "ON"); } else if (cmd == "VREVERSE") { ESP_LOGV(TAG, "updating vflip switch"); vflip_switch_->publish_state(value_string == "ON"); } else if (cmd == "ILLUM") { ESP_LOGV(TAG, "updating illumination switch"); int value = std::stoi(value_string); illumination_switch_->publish_state(value == 1); } else if (cmd == "STANDBYCONF") { ESP_LOGV(TAG, "updating standbyconf switch"); int value = std::stoi(value_string); standbyconf_switch_->publish_state(value == 1); } else if (cmd == "AUTOHOME") { ESP_LOGV(TAG, "updating autohome switch"); int value = std::stoi(value_string); autohome_switch_->publish_state(value == 1); } else if (cmd == "WLPWR") { ESP_LOGV(TAG, "updating wlan power switch"); int value = std::stoi(value_string); wlan_power_switch_->publish_state(value == 1); } else if (cmd == "LOGTO") { ESP_LOGV(TAG, "updating logto switch"); int value = std::stoi(value_string); logto_switch_->publish_state(value == 1); } else if (cmd == "MUTE") { ESP_LOGV(TAG, "updating mute switch with value %s", value_string.c_str()); mute_switch_->publish_state(value_string == "ON"); } else if (cmd == "VOL") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating volume number with value %s", value_string.c_str()); volume_number_->publish_state(value); } else if (cmd == "VKEYSTONE") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating vkeystone number with value %s", value_string.c_str()); vkeystone_number_->publish_state(value); } else if (cmd == "HKEYSTONE") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating hkeystone number with value %s", value_string.c_str()); hkeystone_number_->publish_state(value); } else if (cmd == "BRIGHT") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating brightness number with value %s", value_string.c_str()); if (brightness_number_ != nullptr) brightness_number_->publish_state(value); } else if (cmd == "CONTRAST") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating contrast number with value %s", value_string.c_str()); contrast_number_->publish_state(value); } else if (cmd == "DENSITY") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating density number with value %s", value_string.c_str()); density_number_->publish_state(value); } else if (cmd == "TINT") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating tint number with value %s", value_string.c_str()); tint_number_->publish_state(value); } else if (cmd == "CTEMP") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating ctemp number with value %s", value_string.c_str()); ctemp_number_->publish_state(value); } else if (cmd == "FCOLOR") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating fcolor number with value %s", value_string.c_str()); fcolor_number_->publish_state(value); } else if (cmd == "NRS") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating nrs number with value %s", value_string.c_str()); nrs_number_->publish_state(value); } else if (cmd == "MPEGNRS") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating mpegngs number with value %s", value_string.c_str()); mpegnrs_number_->publish_state(value); } else if (cmd == "OFFSETR") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating offsetr number with value %s", value_string.c_str()); offsetr_number_->publish_state(value); } else if (cmd == "OFFSETG") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating offsetg number with value %s", value_string.c_str()); offsetg_number_->publish_state(value); } else if (cmd == "OFFSETB") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating offsetb number with value %s", value_string.c_str()); oggsetb_number_->publish_state(value); } else if (cmd == "GAINR") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating gainr number with value %s", value_string.c_str()); gainr_number_->publish_state(value); } else if (cmd == "GAING") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating gaing number with value %s", value_string.c_str()); gaing_number_->publish_state(value); } else if (cmd == "GAINB") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating gainb number with value %s", value_string.c_str()); gainb_number_->publish_state(value); } else if (cmd == "SHRF") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating shrf number with value %s", value_string.c_str()); shrf_number_->publish_state(value); } else if (cmd == "SHRS") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating shrs number with value %s", value_string.c_str()); shrs_number_->publish_state(value); } else if (cmd == "DERANGE") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating derange number with value %s", value_string.c_str()); derange_number_->publish_state(value); } else if (cmd == "DESTRENGTH") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating destrength number with value %s", value_string.c_str()); destrength_number_->publish_state(value); } 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