#include "esphome/core/log.h" #include "hauslane.h" namespace esphome { namespace hauslane { static const char *TAG = "hauslane"; void Hauslane::setup() { // initialize the input pins if (this->pin_timer) { pin_timer->setup(); pin_timer->digital_write(false); } if (this->pin_up) { pin_up->setup(); pin_up->digital_write(false); } if (this->pin_down) { pin_down->setup(); pin_down->digital_write(false); } if (this->pin_light) { pin_light->setup(); pin_light->digital_write(false); } if (this->pin_power) { pin_power->setup(); pin_power->digital_write(false); } // register ESPHome API service named "command" register_service(&Hauslane::command, "command", {"command"}); } void Hauslane::loop() { while (this->available()) { uint8_t c; this->read_byte(&c); if (reading==true) { this->rx_message.push_back(c); if (c == MSG_END[pos]) { if (pos+1 == END_LEN) { parse_state(); reading = false; pos = 0; } else { pos++; } } else { pos = 0; } } else { if (c==MSG_START[pos]) { if (pos+1 == START_LEN) { reading = true; pos = 0; } else { pos++; } } else { pos = 0; } } // execute new button press every DELAY ms if needed to adjust state if (millis() - this->last_press > DELAY) { if (this->last_button != NULL) { // button already pressed, so de-activate button button_press(this->last_button, false); } else if (this->meet_target) { // adjustment of speed/light requested, so press buttons until // reaching speed_target and light_target if (this->speed != this->speed_target) { // adjust speed as necessary if (this->speed_target==0 && !this->light_cur) { // request fan turn off and light is off, so simply hit power button button_press(pin_power, true); } else if (this->speed == 0 && !this->light_cur) { if (this->power) { // power already on, so activate fan by pressing down button button_press(pin_down, true); } else { // first activate by pressing power button button_press(pin_power, true); } } else if (this->speed < this->speed_target) { // increase speed button_press(pin_up, true); } else if (this->speed > this->speed_target) { // decrease speed button_press(pin_down, true); } } else if (this->light_cur != this->light_target) { if (this->power && !this->light_target) { // request light turn off and fan is off, so simply hit power button button_press(pin_power, true); } else if (!this->power && this->light_target) { // power on hood first button_press(pin_power, true); } else if (this->power) { // press light button button_press(pin_light, true); } } else { // target already met, so reset flag this->meet_target=false; } } else if (this->speed==0 && !this->light_cur && this->power) { // reset power flag this->power=false; ESP_LOGD(TAG, "Hood power: %d", this->power); } } } } // Components are required to dump their configuration using ESP_LOGCONFIG in // the dump_config() method. This method is used exclusively to print values // determined during setup() -- nothing more. // dump_config() for parent component void Hauslane::dump_config() { ESP_LOGCONFIG(TAG, "Hauslane"); } // turns the light on/off void Hauslane::set_light(bool binary) { if (this->light_target != binary) { this->light_target = binary; this->meet_target = true; } } // set the speed of the fan 0=off, 6=full void Hauslane::set_speed(int new_speed) { if (this->speed_target != new_speed) { this->speed_target = new_speed; this->meet_target = true; } } // read message on rx line and turn into current state of fan and light void Hauslane::parse_state() { size_t len = rx_message.size()-4; std::string msg; std::vector msg_hex; // hex strings for each possible state in the form of X_Y where X=on/off // (light) and Y=0-6 (fan speed) // admittedly this is very ugly - the communication is almost certainly // not UART, but at 3600 baud, the UART decoder is able to read just // enough of the signal as UART characters to get a unique string for // each state, but they seemingly do not follow any patterns (most likely // due to the bits that are discarded by the decoder as not part of a // legitimate UART message) so are hardcoded for each state // proper reverse-engineering of the protocol would require logic analzer or // oscilloscope, but as there is only one-way communication from the front // panel to the main controller board, this would only be useful if // replacing the front panel entirely const std::vector off_0{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce}; const std::vector on_0{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6}; const std::vector off_1{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce}; const std::vector on_1{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce}; const std::vector off_2{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce}; const std::vector on_2{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xc6}; const std::vector off_3{0x38, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce}; const std::vector on_3{0x38, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce}; const std::vector off_4{0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce, 0xce}; const std::vector on_4{0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce}; const std::vector off_5{0xc6, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xc6, 0xce, 0xce}; const std::vector on_5{0xc6, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xc6}; const std::vector off_6{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0x38, 0xce, 0xce}; const std::vector on_6{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0x38}; // print out formatted hex string to log char buf[5]; for (size_t i=0; i< len; i++) { if (i>0) { msg += " "; } msg_hex.push_back(rx_message[i]); sprintf(buf,"%02X", rx_message[i]); msg += buf; } ESP_LOGV(TAG, "%s", msg.c_str()); // set state of light and fan based on received hex message if (msg_hex==off_0) { set_state(false, 0); } else if (msg_hex==on_0) { set_state(true, 0); } else if (msg_hex==off_1) { set_state(false, 1); } else if (msg_hex==on_1) { set_state(true, 1); } else if (msg_hex==off_2) { set_state(false, 2); } else if (msg_hex==on_2) { set_state(true, 2); } else if (msg_hex==off_3) { set_state(false, 3); } else if (msg_hex==on_3) { set_state(true, 3); } else if (msg_hex==off_4) { set_state(false, 4); } else if (msg_hex==on_4) { set_state(true, 4); } else if (msg_hex==off_5) { set_state(false, 5); } else if (msg_hex==on_5) { set_state(true, 5); } else if (msg_hex==off_6) { set_state(false, 6); } else if (msg_hex==on_6) { set_state(true, 6); } this->rx_message.clear(); } void Hauslane::set_state(bool set_light, uint8_t set_speed) { if (this->light_cur != set_light) { // save current state of light in memory ESP_LOGD(TAG, "Received light state: %d", set_light); this->light_cur = set_light; // send light state to API if it is active if (this->send_light_state) { ESP_LOGD(TAG, "Sending new light state."); this->send_light_state(set_light); } } if (this->speed != set_speed) { // save current state of fan in memory ESP_LOGD(TAG, "Received fan speed: %d", set_speed); this->speed = set_speed; // send fan state to API if it is active if (this->send_fan_speed) { ESP_LOGD(TAG,"Sending new fan speed."); this->send_fan_speed(set_speed); } } } // custom component API commands // simulates a button press for the specified button on front panel via GPIO void Hauslane::command(std::string command) { if (command == "timer" && pin_timer) { button_press(pin_timer, true); } else if (command == "up" && pin_up) { button_press(pin_up, true); } else if (command =="down" && pin_down) { button_press(pin_down, true); } else if (command == "light" && pin_light) { button_press(pin_light, true); } else if (command == "power" && pin_power) { button_press(pin_power, true); } else { ESP_LOGD(TAG, "Invalid button name: %s", command.c_str()); } } // simulate a button press by activating the GPIO then releasing void Hauslane::button_press(GPIOPin *pin, bool val) { if (val) { ESP_LOGD(TAG, "Press button: %d", pin); this->last_button=pin; } else { ESP_LOGD(TAG, "Release button: %d", pin); if (this->last_button==this->pin_power) { this->power= !this->power; } this->last_button=NULL; } pin->digital_write(val); last_press=millis(); } // ESPHome light component #ifdef USE_LIGHT void HauslaneLight::setup() { this->parent->register_light_func( [this](const bool &binary) { this->set_state(binary); } ); } // Components are required to dump their configuration using ESP_LOGCONFIG in // the dump_config() method. This method is used exclusively to print values // determined during setup() -- nothing more. // dump_config() for light component void HauslaneLight::dump_config() { ESP_LOGCONFIG(TAG, "Hauslane"); } // set to light that can turn on/off only light::LightTraits HauslaneLight::get_traits() { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::ON_OFF}); return traits; } // when light tries to write state, use Hauslane::set_light void HauslaneLight::write_state(light::LightState *state) { bool binary; state->current_values_as_binary(&binary); this->parent->set_light(binary); } void HauslaneLight::set_state(bool binary) { auto call = light_state_->make_call(); call.set_state(binary); call.perform(); } #endif // ESPHome fan component #ifdef USE_FAN void HauslaneFan::setup() { this->parent->register_fan_func( [this](const int &speed) { this->set_speed(speed); } ); } // set to fan with no oscillation, speed enabled, no direction, and 6 speeds fan::FanTraits HauslaneFan::get_traits() { return fan::FanTraits(false, true, false, 6); } // when fan tries to change state, use Hauslane::set_speed void HauslaneFan::control(const fan::FanCall &call){ if (*call.get_state()) { if (call.get_speed().has_value()) { this->parent->set_speed(*call.get_speed()); } } else { ESP_LOGD(TAG, "Power fan off by setting speed to 0"); this->parent->set_speed(0); } } // Components are required to dump their configuration using ESP_LOGCONFIG in // the dump_config() method. This method is used exclusively to print values // determined during setup() -- nothing more. // dump_config() for fan component void HauslaneFan::dump_config(){ ESP_LOGCONFIG(TAG, "Hauslane"); } void HauslaneFan::set_speed(int new_speed) { if (new_speed>0) { this->state = true; } else { this->state = false; } this->speed=new_speed; this->publish_state(); } #endif } }