-
-
Save liads/c702fd4b8529991af9cd52d03b694814 to your computer and use it in GitHub Desktop.
#include "esphome.h" | |
static const char *TAG = "electra.climate"; | |
typedef enum IRElectraMode { | |
IRElectraModeCool = 0b001, | |
IRElectraModeHeat = 0b010, | |
IRElectraModeAuto = 0b011, | |
IRElectraModeDry = 0b100, | |
IRElectraModeFan = 0b101, | |
IRElectraModeOff = 0b111 | |
} IRElectraMode; | |
typedef enum IRElectraFan { | |
IRElectraFanLow = 0b00, | |
IRElectraFanMedium = 0b01, | |
IRElectraFanHigh = 0b10, | |
IRElectraFanAuto = 0b11 | |
} IRElectraFan; | |
// That configuration has a total of 34 bits | |
// 33: Power bit, if this bit is ON, the A/C will toggle it's power. | |
// 32-30: Mode - Cool, heat etc. | |
// 29-28: Fan - Low, medium etc. | |
// 27-26: Zeros | |
// 25: Swing On/Off | |
// 24: iFeel On/Off | |
// 23: Zero | |
// 22-19: Temperature, where 15 is 0000, 30 is 1111 | |
// 18: Sleep mode On/Off | |
// 17- 2: Zeros | |
// 1: One | |
// 0: Zero | |
typedef union ElectraCode { | |
uint64_t num; | |
struct { | |
uint64_t zeros1 : 1; | |
uint64_t ones1 : 1; | |
uint64_t zeros2 : 16; | |
uint64_t sleep : 1; | |
uint64_t temperature : 4; | |
uint64_t zeros3 : 1; | |
uint64_t ifeel : 1; | |
uint64_t swing : 1; | |
uint64_t zeros4 : 2; | |
uint64_t fan : 2; | |
uint64_t mode : 3; | |
uint64_t power : 1; | |
}; | |
} ElectraCode; | |
const uint8_t ELECTRA_TEMP_MIN = 16; // Celsius | |
const uint8_t ELECTRA_TEMP_MAX = 30; // Celsius | |
#define ELECTRA_TIME_UNIT 1000 | |
#define ELECTRA_NUM_BITS 34 | |
class ElectraClimate : public climate::Climate, public Component { | |
public: | |
void setup() override | |
{ | |
if (this->sensor_) { | |
this->sensor_->add_on_state_callback([this](float state) { | |
this->current_temperature = state; | |
// current temperature changed, publish state | |
this->publish_state(); | |
}); | |
this->current_temperature = this->sensor_->state; | |
} else | |
this->current_temperature = NAN; | |
// restore set points | |
auto restore = this->restore_state_(); | |
if (restore.has_value()) { | |
restore->apply(this); | |
} else { | |
// restore from defaults | |
this->mode = climate::CLIMATE_MODE_AUTO; | |
// initialize target temperature to some value so that it's not NAN | |
this->target_temperature = roundf(this->current_temperature); | |
} | |
this->active_mode_ = this->mode; | |
} | |
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { | |
this->transmitter_ = transmitter; | |
} | |
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } | |
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } | |
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } | |
/// Override control to change settings of the climate device | |
void control(const climate::ClimateCall &call) override | |
{ | |
if (call.get_mode().has_value()) | |
this->mode = *call.get_mode(); | |
if (call.get_target_temperature().has_value()) | |
this->target_temperature = *call.get_target_temperature(); | |
this->transmit_state_(); | |
this->publish_state(); | |
this->active_mode_ = this->mode; | |
} | |
/// Return the traits of this controller | |
climate::ClimateTraits traits() override | |
{ | |
auto traits = climate::ClimateTraits(); | |
traits.set_supports_current_temperature(this->sensor_ != nullptr); | |
traits.set_supports_auto_mode(true); | |
traits.set_supports_cool_mode(this->supports_cool_); | |
traits.set_supports_heat_mode(this->supports_heat_); | |
traits.set_supports_two_point_target_temperature(false); | |
traits.set_supports_away(false); | |
traits.set_visual_min_temperature(ELECTRA_TEMP_MIN); | |
traits.set_visual_max_temperature(ELECTRA_TEMP_MAX); | |
traits.set_visual_temperature_step(1); | |
return traits; | |
} | |
/// Transmit the state of this climate controller via IR | |
void transmit_state_() | |
{ | |
ElectraCode code = { 0 }; | |
code.ones1 = 1; | |
code.fan = IRElectraFan::IRElectraFanAuto; | |
switch (this->mode) { | |
case climate::CLIMATE_MODE_COOL: | |
code.mode = IRElectraMode::IRElectraModeCool; | |
code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
break; | |
case climate::CLIMATE_MODE_HEAT: | |
code.mode = IRElectraMode::IRElectraModeHeat; | |
code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
break; | |
case climate::CLIMATE_MODE_AUTO: | |
code.mode = IRElectraMode::IRElectraModeAuto; | |
code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
break; | |
case climate::CLIMATE_MODE_OFF: | |
default: | |
code.mode = IRElectraMode::IRElectraModeOff; | |
break; | |
} | |
auto temp = (uint8_t) roundf(clamp(this->target_temperature, ELECTRA_TEMP_MIN, ELECTRA_TEMP_MAX)); | |
code.temperature = temp - 15; | |
ESP_LOGD(TAG, "Sending electra code: %lld", code.num); | |
auto transmit = this->transmitter_->transmit(); | |
auto data = transmit.get_data(); | |
data->set_carrier_frequency(38000); | |
uint16_t repeat = 3; | |
for (uint16_t r = 0; r < repeat; r++) { | |
// Header | |
data->mark(3 * ELECTRA_TIME_UNIT); | |
uint16_t next_value = 3 * ELECTRA_TIME_UNIT; | |
bool is_next_space = true; | |
// Data | |
for (int j = ELECTRA_NUM_BITS - 1; j>=0; j--) | |
{ | |
uint8_t bit = (code.num >> j) & 1; | |
// if current index is SPACE | |
if (is_next_space) { | |
// one is one unit low, then one unit up | |
// since we're pointing at SPACE, we should increase it by a unit | |
// then add another MARK unit | |
if (bit == 1) { | |
data->space(next_value + ELECTRA_TIME_UNIT); | |
next_value = ELECTRA_TIME_UNIT; | |
is_next_space = false; | |
} else { | |
// we need a MARK unit, then SPACE unit | |
data->space(next_value); | |
data->mark(ELECTRA_TIME_UNIT); | |
next_value = ELECTRA_TIME_UNIT; | |
is_next_space = true; | |
} | |
} else { | |
// current index is MARK | |
// one is one unit low, then one unit up | |
if (bit == 1) { | |
data->mark(next_value); | |
data->space(ELECTRA_TIME_UNIT); | |
next_value = ELECTRA_TIME_UNIT; | |
is_next_space = false; | |
} else { | |
data->mark(next_value + ELECTRA_TIME_UNIT); | |
next_value = ELECTRA_TIME_UNIT; | |
is_next_space = true; | |
} | |
} | |
} | |
// Last value must be SPACE | |
data->space(next_value); | |
} | |
// Footer | |
data->mark(4 * ELECTRA_TIME_UNIT); | |
transmit.perform(); | |
} | |
ClimateMode active_mode_; | |
bool supports_cool_{true}; | |
bool supports_heat_{true}; | |
remote_transmitter::RemoteTransmitterComponent *transmitter_; | |
sensor::Sensor *sensor_{nullptr}; | |
}; |
Did you personally utilized a temperature sensor?
No
But it’s possible
Can anyone knows how to add the support for the IR receiver, so a change with the regular remote will be reflected in HA?
Yes, in the github repository I have made https://github.com/omersht/ElectraEspHome
i added the option for decode the IR signal, in case that someone is using the old IR transmitter.
/* #include <iostream>
#include <vector>
#include <string>
#include <cmath>
using namespace std; */
/* struct ac_code {
int fan;
int mode;
int temp;
}; */
// A function to check if a value is within 5% tolerance of a target value
bool within_tolerance(int value, int target) {
return std::abs(value - target) <= (0.05 * target);
}
std::vector<int> decode_ir_signal(std::vector<int>& vec) {
// A constant to store the number of bits in the code
const int ELECTRA_NUM_BITS2 = 34;
std::vector<int> decoded_bits(ELECTRA_NUM_BITS2, 0); // bit has ELECTRA_NUM_BITS elements, all equal to 0
//cout << "\n"; // print a newline
// A constant to store the time unit in microseconds
const int ELECTRA_TIME_UNIT2 = 1000;
// A variable to store the next value to be pushed into the vector
int next_value = 3 * ELECTRA_TIME_UNIT2;
// A boolean flag to indicate whether the next value is a space or not
bool is_next_space = true;
int code_index = 0;
for (int j = 1; j < vec.size() - 1; j++) {
if (j > 5 && std::abs(vec[j]) > 2800) {
break;
}
int x = vec[j];
// if current index is SPACE
if (is_next_space) {
if (within_tolerance(abs(vec[j]), next_value + ELECTRA_TIME_UNIT2)) {
decoded_bits[code_index] = 1;
/* cout << "s1b1 ";
cout << "\n"; // print a newline */
is_next_space = false;
next_value = ELECTRA_TIME_UNIT2;
} else {
decoded_bits[code_index] = 0;
/* cout << "s1b0 ";
cout << "\n"; // print a newline */
next_value = ELECTRA_TIME_UNIT2;
is_next_space = true;
j = j + 1;
}
} else {
if (within_tolerance(abs(vec[j]), next_value)) {
decoded_bits[code_index] = 1;
/* cout << "s0b1 ";
cout << "\n"; // print a newline */
j = j + 1;
next_value = ELECTRA_TIME_UNIT2;
is_next_space = false;
} else {
decoded_bits[code_index] = 0;
/* cout << "s0b0 ";
cout << "\n"; // print a newline */
next_value = ELECTRA_TIME_UNIT2;
is_next_space = true;
}
}
code_index = code_index + 1;
}
return decoded_bits;
}
the yaml should be like this:
esphome:
name: ac-parents-electra
friendly_name: Parents AC
includes:
- ElectraClimate.h
- ElectraDecodeFinal.h
esp8266:
board: esp01_1m
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "UbGyg4="
ota:
- platform: esphome
password: "75ff13a"
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
- ssid: !secret wifi_ssid2
password: !secret wifi_password2
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Ac-Parents-Electra"
password: "ySYKNFeRJZk2"
captive_portal:
sensor:
- platform: dht
model: DHT22
pin: 0
id: dht_sensor
temperature:
name: "AC Parents Temperature"
id: dht_temp
humidity:
name: "AC Parents Humidity"
update_interval: 60s
remote_transmitter:
pin: 3 # RX pin
carrier_duty_percent: 50%
id: my_ir_transmitter
remote_receiver:
pin:
number: 1 # TX pin
inverted: True
mode: INPUT_PULLUP
id: ir_receiver
dump: raw
tolerance: 55%
filter: 500us
on_raw:
then:
- lambda: |-
if ((std::abs(x[0]) > 2800) && (std::abs(x[1]) > 2800)) {// change the 2nd pulse to 2800 to includes bit power=0
// Log the current AC status
ElectraCode current;
switch (id(climate_id).mode) {
case climate::CLIMATE_MODE_COOL:
current.mode=IRElectraMode::IRElectraModeCool;
break;
case climate::CLIMATE_MODE_AUTO:
current.mode = IRElectraMode::IRElectraModeAuto;
break;
case climate::CLIMATE_MODE_HEAT:
current.mode=IRElectraMode::IRElectraModeHeat;
break;
case climate::CLIMATE_MODE_OFF:
current.mode = IRElectraMode::IRElectraModeOff;
break;
case climate::CLIMATE_MODE_DRY:
current.mode = IRElectraMode::IRElectraModeDry;
break;
}
switch (id(climate_id).fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
current.fan=IRElectraFan::IRElectraFanLow;
break;
case climate::CLIMATE_FAN_MEDIUM:
current.fan = IRElectraFan::IRElectraFanMedium;
break;
case climate::CLIMATE_FAN_HIGH:
current.fan=IRElectraFan::IRElectraFanHigh;
break;
}
ESP_LOGD("climate", "Current AC mode: %d", current.mode);
ESP_LOGD("climate", "Current Fan mode: %d", current.fan);
// Log the current target_temperature
float current_target_temperature = id(climate_id).target_temperature;
ESP_LOGD("climate", "Current AC target Temperature: %.1f°C", current_target_temperature);
// decodeding the IR signal
auto decoded_bits = decode_ir_signal(x);
auto mode =0;
for (int i = 1; i <= 3; i++) {
mode = mode << 1 | (decoded_bits[i] & 1);
}
int target_temp=(15 + 8 * decoded_bits[11] + 4 * decoded_bits[12] + 2 * decoded_bits[13] + decoded_bits[14]);
ESP_LOGD("IR Receiver", " target_temp- dec: %lld", target_temp);
ElectraCode code;
code.num = 0;
for (int i = 0; i < decoded_bits.size(); i++) {
code.num = code.num << 1 | (decoded_bits[i] & 1);
}// code.num is reversed of the string decoded_bits
ESP_LOGD("IR Receiver", "Received code dec: %lld", code.num);
ESP_LOGD("IR Receiver", "AC mode from the code: %lld", code.mode);
ESP_LOGD("IR Receiver", "AC fan from the code: %lld", code.fan);
ESP_LOGD("IR Receiver", "AC target Temperature from the code: %lld", code.temperature+15);
// update the target temperature and fan no matter what is the code.power state is
if(code.temperature>0){// In case that the recieved code is not nonsense
id(climate_id).target_temperature = code.temperature + 15;//Temperature, where 15 is 0000, 30 is 1111
}
switch (code.fan) {
case 0:
id(climate_id).fan_mode = esphome::climate::CLIMATE_FAN_LOW;
break;
case 1:
id(climate_id).fan_mode = esphome::climate::CLIMATE_FAN_MEDIUM;
break;
case 2:
id(climate_id).fan_mode = esphome::climate::CLIMATE_FAN_HIGH;
break;
case 3:
id(climate_id).fan_mode = esphome::climate::CLIMATE_FAN_ON;
break;
default:
id(climate_id).fan_mode.reset(); // Optional: if the value is out of range, reset the optional
break;
}
if (code.power){
if (id(climate_id).mode!=climate::CLIMATE_MODE_OFF) {
id(climate_id).mode=climate::CLIMATE_MODE_OFF;
}
else {
if (code.mode == IRElectraMode::IRElectraModeCool) {
id(climate_id).mode = climate::CLIMATE_MODE_COOL;
} else if (code.mode == IRElectraMode::IRElectraModeHeat) {
id(climate_id).mode = climate::CLIMATE_MODE_HEAT;
} else if (code.mode == IRElectraMode::IRElectraModeAuto) {
id(climate_id).mode = climate::CLIMATE_MODE_AUTO;
} else if (code.mode == IRElectraMode::IRElectraModeDry) {
id(climate_id).mode = climate::CLIMATE_MODE_DRY;
} else if (code.mode == IRElectraMode::IRElectraModeFan) {
id(climate_id).mode = climate::CLIMATE_MODE_FAN_ONLY;
} else if (code.mode == IRElectraMode::IRElectraModeOff) {
id(climate_id).mode = climate::CLIMATE_MODE_OFF;
} else if (code.mode == 0) {
id(climate_id).mode = climate::CLIMATE_MODE_OFF;
}
}
ESP_LOGD("IR Receiver", "In the if, so something was changed - mode,fan or temp (code.mode=1 and mode !=off)");
// if((id(climate_id).mode ==code.mode )&&(id(climate_id).target_temperature==code.temperature + 15)&&(id(climate_id).fan_mode==code.fan)){
// ESP_LOGD("IR Receiver", "Nothing was changed - mode,fan or temp");
// id(climate_id).mode = climate::CLIMATE_MODE_OFF;
// }
}
else {//code.power=0
ESP_LOGD("IR Receiver", "In the else statment (code.power=0). no update to the mode");
}
id(climate_id).publish_state();
ESP_LOGD("IR Receiver", "Publish the state");
delay (5000) ;//delay in order not to includes multiple IR signal of the same state
}
climate:
- platform: custom
lambda: |-
auto electra_climate = new ElectraClimate();
electra_climate->set_sensor(id(dht_temp)); // Optional
electra_climate->set_transmitter(id(my_ir_transmitter));
App.register_component(electra_climate);
return {electra_climate};
climates:
- name: "Parents AC"
id: climate_id
@omersht I've been trying to use your code from the Github repository but every time I try to turn on the AC it immediately turn off in the climate object.
In the log file I see:
Living Room air conditioner changed to Heat
and immediately Living Room air conditioner turned off triggered by action Climate: Set HVAC mode
When I do the on/off from the remote control it works fine.
Can you help me to figure out the problem?
@omersht I figured out the problem. I will be happy to discuss.
you may contact me on doron dot schlumm at HP dot com
@omersht I figured out the problem. I will be happy to discuss.
Great, can you tell me what have you discoverd? maybe open an issue on the github page?
https://github.com/omersht/ElectraEspHome