#include "emmeti.h" #include "esphome/components/remote_base/remote_base.h" namespace esphome { namespace emmeti { static const char *const TAG = "emmeti.climate"; // setters uint8_t EmmetiClimate::set_temp_() { return (uint8_t) roundf(clamp(this->target_temperature, EMMETI_TEMP_MIN, EMMETI_TEMP_MAX) - EMMETI_TEMP_MIN); } uint8_t EmmetiClimate::set_mode_() { switch (this->mode) { case climate::CLIMATE_MODE_COOL: return EMMETI_MODE_COOL; case climate::CLIMATE_MODE_DRY: return EMMETI_MODE_DRY; case climate::CLIMATE_MODE_HEAT: return EMMETI_MODE_HEAT; case climate::CLIMATE_MODE_FAN_ONLY: return EMMETI_MODE_FAN; case climate::CLIMATE_MODE_HEAT_COOL: default: return EMMETI_MODE_HEAT_COOL; } } uint8_t EmmetiClimate::set_fan_speed_() { switch (this->fan_mode.value()) { case climate::CLIMATE_FAN_LOW: return EMMETI_FAN_1; case climate::CLIMATE_FAN_MEDIUM: return EMMETI_FAN_2; case climate::CLIMATE_FAN_HIGH: return EMMETI_FAN_3; case climate::CLIMATE_FAN_AUTO: default: return EMMETI_FAN_AUTO; } } uint8_t EmmetiClimate::set_blades_() { if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL) { switch (this->blades_) { case EMMETI_BLADES_1: case EMMETI_BLADES_2: case EMMETI_BLADES_HIGH: this->blades_ = EMMETI_BLADES_HIGH; break; case EMMETI_BLADES_3: case EMMETI_BLADES_MID: this->blades_ = EMMETI_BLADES_MID; break; case EMMETI_BLADES_4: case EMMETI_BLADES_5: case EMMETI_BLADES_LOW: this->blades_ = EMMETI_BLADES_LOW; break; default: this->blades_ = EMMETI_BLADES_FULL; break; } } else { switch (this->blades_) { case EMMETI_BLADES_1: case EMMETI_BLADES_2: case EMMETI_BLADES_HIGH: this->blades_ = EMMETI_BLADES_1; break; case EMMETI_BLADES_3: case EMMETI_BLADES_MID: this->blades_ = EMMETI_BLADES_3; break; case EMMETI_BLADES_4: case EMMETI_BLADES_5: case EMMETI_BLADES_LOW: this->blades_ = EMMETI_BLADES_5; break; default: this->blades_ = EMMETI_BLADES_STOP; break; } } return this->blades_; } uint8_t EmmetiClimate::gen_checksum_() { return (this->set_temp_() + this->set_mode_() + 2) % 16; } // getters float EmmetiClimate::get_temp_(uint8_t temp) { return (float) (temp + EMMETI_TEMP_MIN); } climate::ClimateMode EmmetiClimate::get_mode_(uint8_t mode) { switch (mode) { case EMMETI_MODE_COOL: return climate::CLIMATE_MODE_COOL; case EMMETI_MODE_DRY: return climate::CLIMATE_MODE_DRY; case EMMETI_MODE_HEAT: return climate::CLIMATE_MODE_HEAT; case EMMETI_MODE_HEAT_COOL: return climate::CLIMATE_MODE_HEAT_COOL; case EMMETI_MODE_FAN: return climate::CLIMATE_MODE_FAN_ONLY; default: return climate::CLIMATE_MODE_HEAT_COOL; } } climate::ClimateFanMode EmmetiClimate::get_fan_speed_(uint8_t fan_speed) { switch (fan_speed) { case EMMETI_FAN_1: return climate::CLIMATE_FAN_LOW; case EMMETI_FAN_2: return climate::CLIMATE_FAN_MEDIUM; case EMMETI_FAN_3: return climate::CLIMATE_FAN_HIGH; case EMMETI_FAN_AUTO: default: return climate::CLIMATE_FAN_AUTO; } } climate::ClimateSwingMode EmmetiClimate::get_swing_(uint8_t bitmap) { return (bitmap >> 1) & 0x01 ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } template T EmmetiClimate::reverse_(T val, size_t len) { T result = 0; for (size_t i = 0; i < len; i++) { result |= ((val & 1 << i) != 0) << (len - 1 - i); } return result; } template void EmmetiClimate::add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data) { for (size_t i = len; i > 0; i--) { data->mark(EMMETI_BIT_MARK); data->space((val & (1 << (i - 1))) ? EMMETI_ONE_SPACE : EMMETI_ZERO_SPACE); } } template void EmmetiClimate::add_(T val, esphome::remote_base::RemoteTransmitData *data) { data->mark(EMMETI_BIT_MARK); data->space((val & 1) ? EMMETI_ONE_SPACE : EMMETI_ZERO_SPACE); } template void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data) { this->add_(this->reverse_(val, len), len, data); } bool EmmetiClimate::check_checksum_(uint8_t checksum) { uint8_t expected = this->gen_checksum_(); ESP_LOGV(TAG, "Expected checksum: %X", expected); ESP_LOGV(TAG, "Checksum received: %X", checksum); return checksum == expected; } void EmmetiClimate::transmit_state() { auto transmit = this->transmitter_->transmit(); auto *data = transmit.get_data(); data->set_carrier_frequency(EMMETI_IR_FREQUENCY); data->mark(EMMETI_HEADER_MARK); data->space(EMMETI_HEADER_SPACE); if (this->mode != climate::CLIMATE_MODE_OFF) { this->reverse_add_(this->set_mode_(), 3, data); this->add_(1, data); this->reverse_add_(this->set_fan_speed_(), 2, data); this->add_(this->swing_mode != climate::CLIMATE_SWING_OFF, data); this->add_(0, data); // sleep mode this->reverse_add_(this->set_temp_(), 4, data); this->add_(0, 8, data); // zeros this->add_(0, data); // turbo mode this->add_(1, data); // light this->add_(1, data); // tree icon thingy this->add_(0, data); // blow mode this->add_(0x52, 11, data); // idk data->mark(EMMETI_BIT_MARK); data->space(EMMETI_MESSAGE_SPACE); this->reverse_add_(this->set_blades_(), 4, data); this->add_(0, 4, data); // zeros this->reverse_add_(2, 2, data); // thermometer this->add_(0, 18, data); // zeros this->reverse_add_(this->gen_checksum_(), 4, data); } else { this->add_(9, 12, data); this->add_(0, 8, data); this->add_(0x2052, 15, data); data->mark(EMMETI_BIT_MARK); data->space(EMMETI_MESSAGE_SPACE); this->add_(0, 8, data); this->add_(1, 2, data); this->add_(0, 18, data); this->add_(0x0C, 4, data); } data->mark(EMMETI_BIT_MARK); data->space(0); transmit.perform(); } bool EmmetiClimate::parse_state_frame_(EmmetiState curr_state) { this->mode = this->get_mode_(curr_state.mode); this->fan_mode = this->get_fan_speed_(curr_state.fan_speed); this->target_temperature = this->get_temp_(curr_state.temp); this->swing_mode = this->get_swing_(curr_state.bitmap); // this->blades_ = curr_state.fan_pos; if (!(curr_state.bitmap & 0x01)) { this->mode = climate::CLIMATE_MODE_OFF; } this->publish_state(); return true; } bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { if (!data.expect_item(EMMETI_HEADER_MARK, EMMETI_HEADER_SPACE)) { return false; } ESP_LOGD(TAG, "Received emmeti frame"); EmmetiState curr_state; for (size_t pos = 0; pos < 3; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.mode |= 1 << pos; } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } ESP_LOGD(TAG, "Mode: %d", curr_state.mode); if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.bitmap |= 1 << 0; } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } ESP_LOGD(TAG, "On: %d", curr_state.bitmap & 0x01); for (size_t pos = 0; pos < 2; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.fan_speed |= 1 << pos; } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } ESP_LOGD(TAG, "Fan speed: %d", curr_state.fan_speed); for (size_t pos = 0; pos < 2; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.bitmap |= 1 << (pos + 1); } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01); ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01); for (size_t pos = 0; pos < 4; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.temp |= 1 << pos; } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } ESP_LOGD(TAG, "Temp: %d", curr_state.temp); for (size_t pos = 0; pos < 8; pos++) { if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } for (size_t pos = 0; pos < 4; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { curr_state.bitmap |= 1 << (pos + 3); } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01); ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01); ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01); ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01); uint16_t control_data = 0; for (size_t pos = 0; pos < 11; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { control_data |= 1 << pos; } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { return false; } } if (control_data != 0x250) { return false; } return this->parse_state_frame_(curr_state); } } // namespace emmeti } // namespace esphome