mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Emmeti infrared climate support (#5197)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
3abf2f1d14
commit
b34b10888b
9 changed files with 476 additions and 0 deletions
|
@ -103,6 +103,7 @@ esphome/components/duty_time/* @dudanov
|
|||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
esphome/components/emmeti/* @E440QF
|
||||
esphome/components/ens160/* @vincentscode
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
|
|
0
esphome/components/emmeti/__init__.py
Normal file
0
esphome/components/emmeti/__init__.py
Normal file
21
esphome/components/emmeti/climate.py
Normal file
21
esphome/components/emmeti/climate.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate_ir
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@E440QF"]
|
||||
AUTO_LOAD = ["climate_ir"]
|
||||
|
||||
emmeti_ns = cg.esphome_ns.namespace("emmeti")
|
||||
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EmmetiClimate),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
316
esphome/components/emmeti/emmeti.cpp
Normal file
316
esphome/components/emmeti/emmeti.cpp
Normal file
|
@ -0,0 +1,316 @@
|
|||
#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<float>(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<typename T> 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<typename T> 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<typename T> 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<typename T>
|
||||
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
|
109
esphome/components/emmeti/emmeti.h
Normal file
109
esphome/components/emmeti/emmeti.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace emmeti {
|
||||
|
||||
const uint8_t EMMETI_TEMP_MIN = 16; // Celsius
|
||||
const uint8_t EMMETI_TEMP_MAX = 30; // Celsius
|
||||
|
||||
// Modes
|
||||
|
||||
enum EmmetiMode : uint8_t {
|
||||
EMMETI_MODE_HEAT_COOL = 0x00,
|
||||
EMMETI_MODE_COOL = 0x01,
|
||||
EMMETI_MODE_DRY = 0x02,
|
||||
EMMETI_MODE_FAN = 0x03,
|
||||
EMMETI_MODE_HEAT = 0x04,
|
||||
};
|
||||
|
||||
// Fan Speed
|
||||
|
||||
enum EmmetiFanMode : uint8_t {
|
||||
EMMETI_FAN_AUTO = 0x00,
|
||||
EMMETI_FAN_1 = 0x01,
|
||||
EMMETI_FAN_2 = 0x02,
|
||||
EMMETI_FAN_3 = 0x03,
|
||||
};
|
||||
|
||||
// Fan Position
|
||||
|
||||
enum EmmetiBlades : uint8_t {
|
||||
EMMETI_BLADES_STOP = 0x00,
|
||||
EMMETI_BLADES_FULL = 0x01,
|
||||
EMMETI_BLADES_1 = 0x02,
|
||||
EMMETI_BLADES_2 = 0x03,
|
||||
EMMETI_BLADES_3 = 0x04,
|
||||
EMMETI_BLADES_4 = 0x05,
|
||||
EMMETI_BLADES_5 = 0x06,
|
||||
EMMETI_BLADES_LOW = 0x07,
|
||||
EMMETI_BLADES_MID = 0x09,
|
||||
EMMETI_BLADES_HIGH = 0x11,
|
||||
};
|
||||
|
||||
// IR Transmission
|
||||
const uint32_t EMMETI_IR_FREQUENCY = 38000;
|
||||
const uint32_t EMMETI_HEADER_MARK = 9076;
|
||||
const uint32_t EMMETI_HEADER_SPACE = 4408;
|
||||
const uint32_t EMMETI_BIT_MARK = 660;
|
||||
const uint32_t EMMETI_ONE_SPACE = 1630;
|
||||
const uint32_t EMMETI_ZERO_SPACE = 530;
|
||||
const uint32_t EMMETI_MESSAGE_SPACE = 20000;
|
||||
|
||||
struct EmmetiState {
|
||||
uint8_t mode = 0;
|
||||
uint8_t bitmap = 0;
|
||||
uint8_t fan_speed = 0;
|
||||
uint8_t temp = 0;
|
||||
uint8_t fan_pos = 0;
|
||||
uint8_t th = 0;
|
||||
uint8_t checksum = 0;
|
||||
};
|
||||
|
||||
class EmmetiClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
EmmetiClimate()
|
||||
: climate_ir::ClimateIR(EMMETI_TEMP_MIN, EMMETI_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller
|
||||
void transmit_state() override;
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(EmmetiState curr_state);
|
||||
|
||||
// setters
|
||||
uint8_t set_mode_();
|
||||
uint8_t set_temp_();
|
||||
uint8_t set_fan_speed_();
|
||||
uint8_t gen_checksum_();
|
||||
uint8_t set_blades_();
|
||||
|
||||
// getters
|
||||
climate::ClimateMode get_mode_(uint8_t mode);
|
||||
climate::ClimateFanMode get_fan_speed_(uint8_t fan);
|
||||
void get_blades_(uint8_t fanpos);
|
||||
// get swing
|
||||
climate::ClimateSwingMode get_swing_(uint8_t bitmap);
|
||||
float get_temp_(uint8_t temp);
|
||||
|
||||
// check if the received frame is valid
|
||||
bool check_checksum_(uint8_t checksum);
|
||||
|
||||
template<typename T> T reverse_(T val, size_t len);
|
||||
|
||||
template<typename T> void add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *ata);
|
||||
|
||||
template<typename T> void add_(T val, esphome::remote_base::RemoteTransmitData *data);
|
||||
|
||||
template<typename T> void reverse_add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data);
|
||||
|
||||
uint8_t blades_ = EMMETI_BLADES_STOP;
|
||||
};
|
||||
|
||||
} // namespace emmeti
|
||||
} // namespace esphome
|
14
tests/components/emmeti/common.yaml
Normal file
14
tests/components/emmeti/common.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
remote_transmitter:
|
||||
id: tx
|
||||
pin: ${remote_transmitter_pin}
|
||||
carrier_duty_percent: 100%
|
||||
|
||||
remote_receiver:
|
||||
id: rcvr
|
||||
pin: ${remote_receiver_pin}
|
||||
|
||||
climate:
|
||||
- platform: emmeti
|
||||
name: Emmeti
|
||||
receiver_id: rcvr
|
||||
transmitter_id: tx
|
5
tests/components/emmeti/test.esp32-idf.yaml
Normal file
5
tests/components/emmeti/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
remote_transmitter_pin: GPIO33
|
||||
remote_receiver_pin: GPIO32
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/emmeti/test.esp32.yaml
Normal file
5
tests/components/emmeti/test.esp32.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
remote_transmitter_pin: GPIO33
|
||||
remote_receiver_pin: GPIO32
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/emmeti/test.esp8266.yaml
Normal file
5
tests/components/emmeti/test.esp8266.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
remote_transmitter_pin: GPIO4
|
||||
remote_receiver_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
Loading…
Reference in a new issue