#include "tw7100.h" #include "esphome/core/log.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #ifdef USE_SELECT #include "select/tw7100_select.h" #endif 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::vector pwr_off_query_cmds{ "PWR?", "LAMP?" }; std::vector pwr_on_query_cmds{ #ifdef USE_SENSOR "SIGNAL?", #endif #ifdef USE_NUMBER "VOL?", "VKEYSTONE?", "HKEYSTONE?", "BRIGHT?", "CONTRAST?", "DENSITY?", "TINT?", "CTEMP?", "FCOLOR?", "NRS?", "MPEGNRS?", "OFFSETR?", "OFFSETG?", "OFFSETB?", "GAINR?", "GAING?", "GAINB?", "SHRF?", "SHRS?", "DERANGE?", "DESTRENGTH?", #endif #ifdef USE_SWITCH "BTAUDIO?", "4KENHANCE?", "IMGPROC?", "AUDIOOUT?", "HREVERSE?", "VREVERSE?", "ILLUM?", "STANDBYCONF?", "AUTOHOME?", "WLPWR?", "LOGTO?", "MUTE?", #endif #ifdef USE_SELECT "LUMINANCE?", "SOURCE?", "ASPECT?", "OVSCAN?", "CMODE?", "GAMMA?", "IMGPRESET?", "MCFI?", "CLRSPACE?", "DYNRANGE?", "MSEL?", "SPEED?", #endif // TODO: // "HDRPQ?", // number 01-16 // "HDRHLG?", // number 01-16 // "CSEL?", // "SNO?" // Pushed on demand if sensor value is empty (e.g. after boot-up) /* "PRODUCT?", // produces ERR "SOURCELIST?" // Parser present in git history but removed until esphome can update selects after api connections are established "SOURCELISTA?", // same as SOURCELIST "QC?", "CORRECTMET?", */ }; void tw7100Component::setup() { static const char *const TAG = "setup()"; ESP_LOGV(TAG, "SETUP"); #ifdef USE_SELECT 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(); #endif }; 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)) { #ifdef USE_TEXT_SENSOR if (this->serial_->state.length() < 1) { cmd_queue.push_back("SNO?"); } #endif 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); #ifdef USE_BINARY_SENSOR powered_->publish_state(value > 0); #endif #ifdef USE_SWITCH power_switch_->publish_state(value > 0); // ack previous cmd or update switch #endif #ifdef USE_SENSOR 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); #endif #ifdef USE_SELECT } 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"); #endif #ifdef USE_TEXT_SENSOR } else if (cmd == "SNO") { serial_->publish_state(value_string); #endif #ifdef USE_SWITCH } 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"); #endif #ifdef USE_NUMBER } 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); #endif } 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