#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; 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?", /* "QC?", "CORRECTMET?", "ASPECT?", */ "LUMINANCE?", /* "OVSCAN?", */ // // 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?", // "SOURCELIST?" // Pushed on demand if sensor value is empty (e.g. after boot-up) // "SOURCELISTA?", // same as SOURCELIST "LOGTO?", // produces ERR // "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"); source_select_->setup(); luminance_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?"); } 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; 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) {; ESP_LOGW(TAG, "found ERR response, pushing request to fetch full error"); cmd_queue.push_front("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") { 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) { 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); auto call = luminance_select_->make_call(); call.set_option(luminance_select_->luminance[value_string]); call.perform(); } 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 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 == "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 == "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()); 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 == "MPRGNRS") { int value = std::stoi(value_string); ESP_LOGV(TAG, "updating mprgngs 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