#include "optoma_rs232.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" namespace esphome { namespace optoma_rs232 { [[maybe_unused]] static const char *const TAG = "optoma_rs232"; enum QUERIES { INFO = 0, TEMP, LAMP_TIME, FAN_1, }; constexpr const char *QUERY_DATA[] = { "~00150 1\r", // Info "~00150 18\r", // Temp "~00108 1\r", // Lamp time "~00351 1\r", // Fan 1 }; template static void publish(C *c, const M &m) { if (c) c->publish_state(m); } void OptomaRS232Component::publish_input_(const std::string &state) const { publish(beamer_input_select_, state); publish(beamer_input_text_sensor_, state); } void OptomaRS232Component::publish_power_(const bool state) const { publish(beamer_power_binary_sensor_, state); publish(beamer_power_switch_, state); } void OptomaRS232Component::dump_config() { ESP_LOGCONFIG(TAG, "Optoma RS232:"); check_uart_settings(9600); } void OptomaRS232Component::loop() { if (!available()) return; // pos = 0 and crlf: none // pos > 0 and crlf: send // pos < end and not crlf: add // pos == end and not crlf: discard while (available()) { uint8_t c; if (!read_byte(&c)) continue; if (!c) continue; if (c == '\r' || c == '\n') { if (cursor_ > 0) { buffer_[cursor_] = 0; process_line_(buffer_); cursor_ = 0; } } else if (cursor_ < sizeof(buffer_) - 1) { buffer_[cursor_++] = toupper(c); } } } void OptomaRS232Component::update() { last_query_ = (last_query_ + 1) % std::size(QUERY_DATA); write_array(reinterpret_cast(QUERY_DATA[last_query_]), strlen(QUERY_DATA[last_query_])); } void OptomaRS232Component::beamer_input_select_changed(const std::string &state, size_t) { const char *data; Inputs inp{UNKNOWN}; if (state == "Unknown") return; if (state == "HDMI 1") { inp = HDMI_1; data = "~0012 1\r"; } else if (state == "HDMI 2") { inp = HDMI_2; data = "~0012 15\r"; } else if (state == "VGA") { inp = VGA; data = "~0012 5\r"; // VGA 1 } else { return; } if (inp == current_input_) return; current_input_ = inp; write_array(reinterpret_cast(data), strlen(data)); } void OptomaRS232Component::beamer_power_switch_changed(const bool state) { const char *cmd = state ? "~0000 1\r" : "~0000 2\r"; write_array(reinterpret_cast(cmd), strlen(cmd)); } void OptomaRS232Component::process_line_(const std::string &str) { // if we are waiting for the projector to respond to a command. // it will respond P (pass) or F (fail) before giving us the actual response to the command. if (str == "P" || str == "F") return; // assuming any commands have been dealt with above, we listen for messages from the projector // the OK-something messages are in response to status queries, sometimes these are in caps, sometimes not // hence the toUpperCase call earlier // the INFO messages come in automatically when the projector changes state if ( // x == "OK1" || // status query returned power on str == "INFO1") { // warming up publish_power_(true); return; } if ( // x == "OK0" || // status query returned power off str == "INFO2" || // cooling down str == "INFO0") { // going into standby publish_power_(false); return; } if (str_startswith(str, "OK")) { ESP_LOGD(TAG, "OK-message: %s", str.c_str()); process_query_response_(str); return; } // ESP_LOGD("projector", "unhandled message: %s", str.c_str()); } void OptomaRS232Component::process_query_response_(const std::string &str) { if (str.length() >= 3) { switch (last_query_) { case QUERIES::TEMP: publish(beamer_temp_sensor_, strtol(str.c_str() + 2, 0, 10)); break; case QUERIES::LAMP_TIME: if (str.length() == 7) publish(beamer_lamp_time_sensor_, strtol(str.c_str() + 2, 0, 10)); break; case QUERIES::FAN_1: publish(beamer_fan1_sensor_, strtol(str.c_str() + 2, 0, 10)); break; case QUERIES::INFO: if (str.length() >= 13) { char buf[17]{}; strncpy(buf, str.c_str(), sizeof(buf)); const auto color_mode = strtol(buf + 14, 0, 10); buf[14] = 0; const auto firmware = strtol(buf + 10, 0, 10); buf[10] = 0; const auto input = strtol(buf + 8, 0, 10); buf[8] = 0; const auto lamp_time = strtol(buf + 3, 0, 10); buf[3] = 0; const auto power = strtol(buf + 2, 0, 10); publish_power_(power); // publish(beamer_firmware_, firmware); if (power) publish(beamer_lamp_time_sensor_, lamp_time); publish(beamer_color_mode_sensor_, color_mode); switch (input) { case Inputs::HDMI_1: current_input_ = Inputs::HDMI_1; publish_input_("HDMI 1"); break; case Inputs::HDMI_2: current_input_ = Inputs::HDMI_2; publish_input_("HDMI 2"); break; case Inputs::VGA: current_input_ = Inputs::VGA; publish_input_("VGA"); break; default: case Inputs::UNKNOWN: current_input_ = Inputs::UNKNOWN; publish_input_("Unknown"); break; } break; } default:; } } } #ifdef USE_SELECT void InputSelect::control(const std::string &value) { this->publish_state(value); auto index = this->index_of(value); if (index.has_value() && this->parent_) this->parent_->beamer_input_select_changed(value, *index); } #endif #ifdef USE_SWITCH void PowerSwitch::write_state(const bool state) { this->publish_state(state); if (this->parent_) this->parent_->beamer_power_switch_changed(state); } #endif } // namespace optoma_rs232 } // namespace esphome