#include "tw7100.h" #include #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 powered = false; 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?", #ifdef USE_SENSOR "LAMP?" #endif }; 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_) source_select_->setup(); if (luminance_select_) luminance_select_->setup(); if (aspect_select_) aspect_select_->setup(); if (cmode_select_) cmode_select_->setup(); if (gamma_select_) gamma_select_->setup(); if (imgpreset_select_) imgpreset_select_->setup(); if (mcfi_select_) mcfi_select_->setup(); if (ovscan_select_) ovscan_select_->setup(); if (clrspace_select_) clrspace_select_->setup(); if (dynrange_select_) dynrange_select_->setup(); if (msel_select_) msel_select_->setup(); if (speed_select_) 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) { #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 && !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) { 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()"; const std::string cmd = data.first; const std::string value_string = data.second; if (cmd == "PWR") { ESP_LOGV(TAG, "updating power sensors"); int value = std::stoi(value_string); powered = (value > 0); #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); #endif #ifdef USE_BINARY_SENSOR has_signal_->publish_state(value > 0); #endif #ifdef USE_SENSOR 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); if(tw7100Select::luminance.find(value_string) != tw7100Select::luminance.end()) { auto call = luminance_select_->make_call(); call.set_option(tw7100Select::luminance[value_string]); call.perform(); } else { ESP_LOGE(TAG, "Result %s not present in options", value_string); } } else if (cmd == "SOURCE") { ESP_LOGV(TAG, "updating source select"); if(tw7100Select::sources.find(value_string) != tw7100Select::sources.end()) { auto call = source_select_->make_call(); call.set_option(tw7100Select::sources[value_string]); call.perform(); } else { ESP_LOGE(TAG, "Result %s not present in options", value_string); } } else if (cmd == "ASPECT") { ESP_LOGV(TAG, "updating aspect select"); if(tw7100Select::aspect.find(value_string) != tw7100Select::aspect.end()) { auto call = aspect_select_->make_call(); call.set_option(tw7100Select::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") { ESP_LOGV(TAG, "updating volume number with value %s", value_string.c_str()); int value = std::stoi(value_string); volume_number_->publish_state(value); } else if (cmd == "VKEYSTONE") { ESP_LOGV(TAG, "updating vkeystone number with value %s", value_string.c_str()); int value = std::stoi(value_string); vkeystone_number_->publish_state(value); } else if (cmd == "HKEYSTONE") { ESP_LOGV(TAG, "updating hkeystone number with value %s", value_string.c_str()); int value = std::stoi(value_string); hkeystone_number_->publish_state(value); } else if (cmd == "BRIGHT") { ESP_LOGV(TAG, "updating brightness number with value %s", value_string.c_str()); int value = std::stoi(value_string); brightness_number_->publish_state(value); } else if (cmd == "CONTRAST") { ESP_LOGV(TAG, "updating contrast number with value %s", value_string.c_str()); int value = std::stoi(value_string); contrast_number_->publish_state(value); } else if (cmd == "DENSITY") { ESP_LOGV(TAG, "updating density number with value %s", value_string.c_str()); int value = std::stoi(value_string); density_number_->publish_state(value); } else if (cmd == "TINT") { ESP_LOGV(TAG, "updating tint number with value %s", value_string.c_str()); int value = std::stoi(value_string); tint_number_->publish_state(value); } else if (cmd == "CTEMP") { ESP_LOGV(TAG, "updating ctemp number with value %s", value_string.c_str()); int value = std::stoi(value_string); ctemp_number_->publish_state(value); } else if (cmd == "FCOLOR") { ESP_LOGV(TAG, "updating fcolor number with value %s", value_string.c_str()); int value = std::stoi(value_string); fcolor_number_->publish_state(value); } else if (cmd == "NRS") { ESP_LOGV(TAG, "updating nrs number with value %s", value_string.c_str()); int value = std::stoi(value_string); nrs_number_->publish_state(value); } else if (cmd == "MPEGNRS") { ESP_LOGV(TAG, "updating mpegngs number with value %s", value_string.c_str()); int value = std::stoi(value_string); mpegnrs_number_->publish_state(value); } else if (cmd == "OFFSETR") { ESP_LOGV(TAG, "updating offsetr number with value %s", value_string.c_str()); int value = std::stoi(value_string); offsetr_number_->publish_state(value); } else if (cmd == "OFFSETG") { ESP_LOGV(TAG, "updating offsetg number with value %s", value_string.c_str()); int value = std::stoi(value_string); offsetg_number_->publish_state(value); } else if (cmd == "OFFSETB") { ESP_LOGV(TAG, "updating offsetb number with value %s", value_string.c_str()); int value = std::stoi(value_string); offsetb_number_->publish_state(value); } else if (cmd == "GAINR") { ESP_LOGV(TAG, "updating gainr number with value %s", value_string.c_str()); int value = std::stoi(value_string); gainr_number_->publish_state(value); } else if (cmd == "GAING") { ESP_LOGV(TAG, "updating gaing number with value %s", value_string.c_str()); int value = std::stoi(value_string); gaing_number_->publish_state(value); } else if (cmd == "GAINB") { ESP_LOGV(TAG, "updating gainb number with value %s", value_string.c_str()); int value = std::stoi(value_string); gainb_number_->publish_state(value); } else if (cmd == "SHRF") { ESP_LOGV(TAG, "updating shrf number with value %s", value_string.c_str()); int value = std::stoi(value_string); shrf_number_->publish_state(value); } else if (cmd == "SHRS") { ESP_LOGV(TAG, "updating shrs number with value %s", value_string.c_str()); int value = std::stoi(value_string); shrs_number_->publish_state(value); } else if (cmd == "DERANGE") { ESP_LOGV(TAG, "updating derange number with value %s", value_string.c_str()); int value = std::stoi(value_string); derange_number_->publish_state(value); } else if (cmd == "DESTRENGTH") { ESP_LOGV(TAG, "updating destrength number with value %s", value_string.c_str()); int value = std::stoi(value_string); 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