385 lines
11 KiB
C++
385 lines
11 KiB
C++
#include "esphome/core/log.h"
|
|
#include "hauslane.h"
|
|
|
|
#if defined(USE_API)
|
|
#include "esphome/components/api/custom_api_device.h"
|
|
#endif
|
|
|
|
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"
|
|
#if defined(USE_API)
|
|
register_service(&Hauslane::command, "command", {"command"});
|
|
#endif
|
|
}
|
|
|
|
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->speed==0 && !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) {
|
|
// request light on and fan off, so power on hood first
|
|
button_press(pin_power, true);
|
|
} else if (this->power) {
|
|
// press light button to toggle
|
|
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<uint8_t> 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<uint8_t> off_0{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_0{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6};
|
|
const std::vector<uint8_t> off_1{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_1{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> off_2{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_2{0xce, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xc6};
|
|
const std::vector<uint8_t> off_3{0x38, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_3{0x38, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> off_4{0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_4{0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce};
|
|
const std::vector<uint8_t> off_5{0xc6, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xc6, 0xc6, 0xce, 0xce};
|
|
const std::vector<uint8_t> on_5{0xc6, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xc6};
|
|
const std::vector<uint8_t> off_6{0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0x38, 0xce, 0xce};
|
|
const std::vector<uint8_t> 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;
|
|
if (!this->meet_target) {
|
|
// sync target light state if changed by button press
|
|
this->light_target = 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;
|
|
if (!this->meet_target) {
|
|
// sync target fan state if changed by button press
|
|
this->speed_target = 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);
|
|
}
|
|
}
|
|
if (!this->power && (set_speed > 0 || set_light)) {
|
|
// set power to on if light or fan are on
|
|
this->power = true;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
}
|
|
}
|