### The problem
Gents
I have a 4 relais from M5Stack. I've started to write… a custom component. I'm almost there, but the build generates errors. Is there someone who can help me?
### Which version of ESPHome has the issue?
2023.2.3
### What type of installation are you using?
Docker
### Which version of Home Assistant has the issue?
_No response_
### What platform are you using?
ESP32
### Board
M5Atom
### Component causing the issue
M5 stack 4 relais
### Example YAML snippet
```yaml
M5Stack_4_relais:
Relais_1:
name: Relais 1
Relais_2:
name: Relais 2
Relais_3:
name: Relais 3
Relais_4:
name: Relais 4
Relais_all:
name: Relais all
```
### Anything in the logs that might be useful for us?
```txt
Folder structure:
M5Stack_4_relais
-M5Stack_4_relais.h
-M5Stack_4_relais.cpp
-__init__.py
--switch (folder)
---M5Stack_switch.cpp
---M5Stack_switch.h
---switch.py
M5Stack_4_relais.h
/*
*******************************************************************************
* Copyright (c) 2021 by M5Stack
* Equipped with Atom-Lite/Matrix sample source code
* Visit for more information: https://docs.m5stack.com/en/unit/4relay
*
* Product: Unit 4RELAY.
* Date: 2022/7/22
*******************************************************************************
Please connect to port ,Control 4 relays and demonstrate the asynchronous
control relay LED
-------------------------------------------------------------------------------
RELAY control reg | 0x10
-----------------------------------------------------------------------------
Relay_ctrl_mode_reg[0] | R/W | System control
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| R | R | R | R | R | R | R | Sync Mode |
| -Sync Mode:0 LED&Relay Async
| -Sync Mode:1 LED&Relay Sync
---------------------------------------------------------------------------------
Relay_ctrl_mode_reg[1] | R/W | Relay & LED control
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| LED1| LED2| LED3| LED4| RLY1| RLY2| RLY3| RLY4|
-------------------------------------------------------------------------------*/
// Note: The relay can only be controlled in synchronous mode, if the relay is
// controlled in asynchronous mode, it will be invalid.
#pragma once
#include "esphome.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/i2c/i2c.h"
#define UNIT_4RELAY_ADDR 0X26
#define UNIT_4RELAY_REG 0X10
#define UNIT_4RELAY_RELAY_REG 0X11
namespace esphome {
namespace M5Stack_4_relais {
class M5Stack_4_relais : public Component, public i2c::I2CDevice {
private:
void write1Byte(uint8_t register_address, uint8_t data);
uint8_t read1Byte(uint8_t register_address);
public:
void switchMode(bool mode);
void set_relais_1(M5Stack_Switch *Relais_1) { Relais_1_ = Relais_1; }
void set_relais_2(M5Stack_Switch *Relais_2) { Relais_2_ = Relais_2; }
void set_relais_3(M5Stack_Switch *Relais_3) { Relais_3_ = Relais_3; }
void set_relais_4(M5Stack_Switch *Relais_4) { Relais_4_ = Relais_4; }
void set_relais_all(M5Stack_Switch *Relais_all) { Relais_all_ = Relais_all; }
protected:
M5Stack_Switch *Relais_1_{1};
M5Stack_Switch *Relais_2_{2};
M5Stack_Switch *Relais_3_{3};
M5Stack_Switch *Relais_4_{4};
M5Stack_Switch *Relais_4_{0};
uint8_t Relais_ID;
void setup() override;
void loop() override;
void dump_config() override;
void Init(bool mode);
void relayWrite(uint8_t number, bool state);
void relayAll(bool state);
void ledWrite(uint8_t number, bool state);
void ledAll(bool state);
}
} // namespace M5Stack
} // namespace esphome
-----------------------------------------------
M5Stack_4_relais.cpp
#include "M5stack_4_relais.h"
namespace esphome {
namespace M5Stack_4_relais {
/*! @brief Write a certain length of data to the specified register address. */
void M5Stack_4_relais::write1Byte(uint8_t Register_address, uint8_t data) {
if (!this->write_byte(Register_address, data)) {
this->mark_failed();
return;
}
}
/*! @brief Setting the mode of the device, and turn off all relays.
* @param mode Async = 0, Sync = 1. */
void M5Stack_4_relais::Init(bool mode) {
write1Byte(UNIT_4RELAY_ADDR, UNIT_4RELAY_REG, mode);
write1Byte(UNIT_4RELAY_ADDR, UNIT_4RELAY_RELAY_REG, 0);
}
/*! @brief Setting the mode of the device.
* @param mode Async = 0, Sync = 1. */
void M5Stack_4_relais::switchMode(bool mode) { write1Byte(UNIT_4RELAY_ADDR, UNIT_4RELAY_REG, mode); }
/*! @brief Read a certain length of data to the specified register address. */
uint8_t M5Stack_4_relais::read1Byte(uint8_t Register_address) {
if (!this->read_bytes(Register_address, data, uint8_t(1))) {
this->mark_failed();
return uint8_t(0);
}
return data;
}
/*! @brief Set the mode of all relays at the same time.
* @param state OFF = 0, ON = 1. */
void M5Stack_4_relais::relayAll(bool state) { write1Byte(UNIT_4RELAY_RELAY_REG, state * (0x0f)); }
/*! @brief Set the mode of all leds at the same time.
* @param state OFF = 0, ON = 1. */
void M5Stack_4_relais::ledAll(bool state) { write1Byte(UNIT_4RELAY_RELAY_REG, state * (0xf0)); }
/*! @brief Control the on/off of the specified relay.
* @param number Bit number of relay (0~3).
@param state OFF = 0, ON = 1 . */
void M5Stack_4_relais::relayWrite(uint8_t number, bool state) {
uint8_t StateFromDevice = read1Byte(UNIT_4RELAY_RELAY_REG);
if (state == 0) {
StateFromDevice &= ~(0x01 << number);
} else {
StateFromDevice |= (0x01 << number);
}
write1Byte(UNIT_4RELAY_RELAY_REG, StateFromDevice);
}
/*! @brief Control the on/off of the specified led.
* @param number Bit number of led (0~3).
@param state OFF = 0, ON = 1 . */
void M5Stack_4_relais::ledWrite(uint8_t number, bool state) {
uint8_t StateFromDevice = read1Byte(UNIT_4RELAY_RELAY_REG);
if (state == 0) {
StateFromDevice &= ~(UNIT_4RELAY_REG << number);
} else {
StateFromDevice |= (UNIT_4RELAY_REG << number);
}
write1Byte(UNIT_4RELAY_RELAY_REG, StateFromDevice);
}
void M5Stack_4_relais::setup() {
ESP_LOGCONFIG(TAG, "Setting up M5stack_4_relais...");
this->Init(1);
}
void EmptyI2CComponent::loop() {}
void M5Stack_4_relais::dump_config() {
ESP_LOGCONFIG(TAG, "M5stack_4_relais:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with M5stack_4_relais failed!");
}
LOG_SWITCH(" ", "Relais", this->);
} // namespace M5Stack
} // namespace esphome
---------------------------------------------------
__init__.py
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, switch
from esphome.const import CONF_ID
DEPENDENCIES = ['i2c']
CONF_I2C_ADDR = 0X26
CONF_M5Stack_4_relais_ID = "M5Stack_4_relais_id"
M5Stack_ns = cg.esphome_ns.namespace("M5Stack_4_relais")
M5Stack_4_relais = M5Stack_ns.class_("M5Stack_4_relais", cg.Component, i2c.I2CDevice)
CONF_Relais_1 = 'Relais_1'
CONF_Relais_2 = 'Relais_2'
CONF_Relais_3 = 'Relais_3'
CONF_Relais_4 = 'Relais_4'
CONF_Relais_all = 'Relais_all'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(M5Stack_4_relais),
cv.Optional(CONF_Relais_1): switch.switch_schema(),
cv.Optional(CONF_Relais_2): switch.switch_schema(),
cv.Optional(CONF_Relais_3): switch.switch_schema(),
cv.Optional(CONF_Relais_4): switch.switch_schema(),
cv.Optional(CONF_Relais_all): switch.switch_schema()
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(CONF_I2C_ADDR))
SWITCH_MAP = {
CONF_Relais_1: "set_relais_1",
CONF_Relais_2: "set_relais_2",
CONF_Relais_3: "set_relais_3",
CONF_Relais_4: "set_relais_4",
CONF_Relais_all: "set_relais_all",
}
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
# for key, funcName in SWITCH_MAP.items():
# if key in config:
# sens = await switch.new_switch(config[key])
# cg.add(getattr(var, funcName)(sens))
--------------------------------------------------
M5Stack_switch.cpp
#include "M5Stack_switch.h"
#include "esphome/core/log.h"
namespace esphome {
namespace M5Stack_4_relais {
static const char *const TAG = "switch.M5Stack_4_relais";
float M5Stack_Switch::get_setup_priority() const { return setup_priority::HARDWARE; }
void M5Stack_Switch::setup() {
ESP_LOGCONFIG(TAG, "Setting up M5Stack_4_relais Switch '%s'...", this->name_.c_str());
bool initial_state = this->get_initial_state_with_restore_mode().value_or(false);
// write state before setup
if (initial_state) {
this->turn_on();
} else {
this->turn_off();
}
}
void M5Stack_Switch::dump_config() {
LOG_SWITCH("", "M5Stack_4_relais Switch", this);
if (!this->interlock_.empty()) {
ESP_LOGCONFIG(TAG, " Interlocks:");
for (auto *lock : this->interlock_) {
if (lock == this)
continue;
ESP_LOGCONFIG(TAG, " %s", lock->get_name().c_str());
}
}
}
void M5Stack_Switch::write_state(bool state) {
if (state != this->inverted_) {
// Turning ON, check interlocking
bool found = false;
for (auto *lock : this->interlock_) {
if (lock == this)
continue;
if (lock->state) {
lock->turn_off();
found = true;
}
}
if (found && this->interlock_wait_time_ != 0) {
this->set_timeout("interlock", this->interlock_wait_time_, [this, state] {
// Don't write directly, call the function again
// (some other switch may have changed state while we were waiting)
this->write_state(state);
});
return;
}
} else if (this->interlock_wait_time_ != 0) {
// If we are switched off during the interlock wait time, cancel any pending
// re-activations
this->cancel_timeout("interlock");
}
this->publish_state(state);
}
void M5Stack_Switch::set_interlock(const std::vector<Switch *> &interlock) { this->interlock_ = interlock; }
} // namespace M5Stack
} // namespace esphome
--------------------------------------------
M5Stack_switch.h
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/switch/switch.h"
#include "../M5Stack_4_relais.h"
#include <vector>
namespace esphome {
namespace M5Stack_4_relais {
class M5Stack_Switch : public switch_::Switch, public Component {
uint8_t *RelayID_;
public:
void set_M5Stack_4_Relais(M5Stack_4_relais *m5stack_4_relais) { m5stack_4_relais_ = m5stack_4_relais; }
void set_RelayID(uint8_t *RelayID) { RelayID_ = RelayID; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
float get_setup_priority() const override;
void setup() override;
void dump_config() override;
void set_interlock(const std::vector<Switch *> &interlock);
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
protected:
void write_state(bool state) override;
M5Stack_4_relais *m5stack_4_relais_;
std::vector<Switch *> interlock_;
uint32_t interlock_wait_time_{0};
void write_state(bool state) override {
// This will be called every time the user requests a state change.
if (RelayID_ == 0) {
m5stack_4_relais_->relayAll();
m5stack_4_relais_->ledAll();
} else {
m5stack_4_relais_->relayWrite(RelayID_ - 1, state);
m5stack_4_relais_->ledWrite(RelayID_ + 3, state);
}
// Acknowledge new state by publishing it
publish_state(state);
}
};
} // namespace M5Stack
} // namespace esphome
--------------------------------------
switch.py
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import CONF_ID, CONF_INTERLOCK
from .. import M5Stack_ns, M5Stack_4_relais, CONF_M5Stack_4_relais_ID
M5StackSwitch = M5Stack_ns.class_("M5Stack_Switch", switch.Switch, cg.Component)
CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time"
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(M5StackSwitch),
cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component),
cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)),
cv.Optional(CONF_INTERLOCK_WAIT_TIME, default="0ms"): cv.positive_time_period_milliseconds
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await switch.new_switch(config)
await cg.register_component(var, config)
if CONF_INTERLOCK in config:
interlock = []
for it in config[CONF_INTERLOCK]:
lock = await cg.get_variable(it)
interlock.append(lock)
cg.add(var.set_interlock(interlock))
cg.add(var.set_interlock_wait_time(config[CONF_INTERLOCK_WAIT_TIME]))
--------------------------------------------------
I get the error message:
--------------------------------------------------------------------------------
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
- toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch3
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
Dependency Graph
|-- AsyncTCP-esphome @ 1.2.2
|-- WiFi @ 2.0.0
|-- FS @ 2.0.0
|-- Update @ 2.0.0
|-- ESPAsyncWebServer-esphome @ 2.1.0
| |-- AsyncTCP-esphome @ 1.2.2
|-- DNSServer @ 2.0.0
|-- ESPmDNS @ 2.0.0
|-- noise-c @ 0.1.4
| |-- libsodium @ 1.10018.1
|-- Wire @ 2.0.0
Compiling .pioenvs/coming-soon-dmx/src/esphome/components/M5Stack_4_relais/M5Stack_4_relais.cpp.o
src/esphome/components/M5Stack_4_relais/M5Stack_4_relais.cpp:1:10: fatal error: M5stack_4_relais.h: No such file or directory
**************************************************************************
* Looking for M5stack_4_relais.h dependency? Check our library registry!
*
* CLI > platformio lib search "header:M5stack_4_relais.h"
* Web > https://registry.platformio.org/search?q=header:M5stack_4_relais.h
*
**************************************************************************
#include "M5stack_4_relais.h"
^~~~~~~~~~~~~~~~~~~~
compilation terminated.
Compiling .pioenvs/coming-soon-dmx/src/esphome/components/api/api_pb2_service.cpp.o
Compiling .pioenvs/coming-soon-dmx/src/esphome/components/api/api_server.cpp.o
*** [.pioenvs/coming-soon-dmx/src/esphome/components/M5Stack_4_relais/M5Stack_4_relais.cpp.o] Error 1
Compiling .pioenvs/coming-soon-dmx/src/esphome/components/api/list_entities.cpp.o
========================== [FAILED] Took 9.58 seconds ==========================
```
### Additional information
_No response_