mirror of
https://github.com/esphome/esphome.git
synced 2026-07-01 21:12:50 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f69fea037a | |||
| d2178a157b | |||
| 49d6718345 |
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.25
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.23
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -8,10 +8,8 @@ from esphome.components.modbus.helpers import (
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_ALLOW_PARTIAL_READ,
|
||||
CONF_COURTESY_RESPONSE,
|
||||
CONF_READ_LAMBDA,
|
||||
CONF_REGISTER_LAST_ADDRESS,
|
||||
@@ -43,62 +41,17 @@ SERVER_COURTESY_RESPONSE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# RAW has no numeric encoding, so it is not a valid server register type: a server value is produced by a
|
||||
# lambda and encoded into registers, and on the server a RAW register would just be a single 16-bit word --
|
||||
# use U_WORD for that. Restrict the choices to the encodable types.
|
||||
SERVER_SENSOR_VALUE_TYPE = {
|
||||
key: value for key, value in SENSOR_VALUE_TYPE.items() if key != "RAW"
|
||||
}
|
||||
|
||||
ModbusServerRegisterSchema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ServerRegister),
|
||||
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
SERVER_SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||
cv.Required(CONF_READ_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_ALLOW_PARTIAL_READ, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate_register_ranges(config: ConfigType) -> ConfigType:
|
||||
# Each register occupies [address, address + register_count); the whole span must fit inside the 16-bit
|
||||
# Modbus address space (0x0000-0xFFFF).
|
||||
for register in config.get(CONF_REGISTERS, []):
|
||||
address = register[CONF_ADDRESS]
|
||||
register_count = TYPE_REGISTER_MAP[register[CONF_VALUE_TYPE]]
|
||||
if address + register_count > 0x10000:
|
||||
raise cv.Invalid(
|
||||
f"Register at 0x{address:04X} spans {register_count} register(s) and runs past "
|
||||
"the end of the 16-bit address space (0xFFFF)",
|
||||
path=[CONF_REGISTERS],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _validate_no_overlapping_registers(config: ConfigType) -> ConfigType:
|
||||
# Each register occupies [address, address + register_count). Reject configs where any two ranges
|
||||
# overlap -- the same address twice, or a multi-register value straddling a neighbour -- since the
|
||||
# server resolves a request by the value containing an address and overlaps are ambiguous.
|
||||
spans = sorted(
|
||||
(register[CONF_ADDRESS], TYPE_REGISTER_MAP[register[CONF_VALUE_TYPE]])
|
||||
for register in config.get(CONF_REGISTERS, [])
|
||||
)
|
||||
for (address, register_count), (next_address, _) in zip(
|
||||
spans, spans[1:], strict=False
|
||||
):
|
||||
if next_address < address + register_count:
|
||||
raise cv.Invalid(
|
||||
f"Register address 0x{next_address:04X} overlaps the register at 0x{address:04X}, "
|
||||
f"which spans {register_count} register(s); each register's address range must be unique",
|
||||
path=[CONF_REGISTERS],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -109,12 +62,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.ensure_list(ModbusServerRegisterSchema),
|
||||
}
|
||||
).extend(modbus.modbus_device_schema(0x01, role="server")),
|
||||
_validate_register_ranges,
|
||||
_validate_no_overlapping_registers,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config):
|
||||
return modbus.final_validate_modbus_device("modbus_server", role="server")(config)
|
||||
|
||||
|
||||
@@ -167,8 +118,6 @@ async def to_code(config):
|
||||
),
|
||||
)
|
||||
)
|
||||
if server_register[CONF_ALLOW_PARTIAL_READ]:
|
||||
cg.add(server_register_var.set_allow_partial_read(True))
|
||||
cg.add(var.add_server_register(server_register_var))
|
||||
await cg.register_component(var, config)
|
||||
return await modbus.register_modbus_server_device(var, config)
|
||||
|
||||
@@ -5,4 +5,3 @@ CONF_COURTESY_RESPONSE = "courtesy_response"
|
||||
CONF_READ_LAMBDA = "read_lambda"
|
||||
CONF_WRITE_LAMBDA = "write_lambda"
|
||||
CONF_REGISTERS = "registers"
|
||||
CONF_ALLOW_PARTIAL_READ = "allow_partial_read"
|
||||
|
||||
@@ -8,25 +8,6 @@ using modbus::helpers::registers_to_number;
|
||||
|
||||
static const char *const TAG = "modbus_server";
|
||||
|
||||
// The widest Modbus value type (QWORD) spans four registers.
|
||||
static constexpr uint8_t MAX_REGISTERS_PER_VALUE = 4;
|
||||
// number_to_payload() encodes the 64-bit value returned by read_lambda() into 16-bit registers, so the
|
||||
// widest possible value spans exactly sizeof(int64_t) / sizeof(uint16_t) registers. Tie the bound to that
|
||||
// source so a future wider value type -- which would require widening the encoded value itself -- can't
|
||||
// silently overflow the value_words buffer below (StaticVector::push_back drops words past capacity).
|
||||
static_assert(MAX_REGISTERS_PER_VALUE == sizeof(int64_t) / sizeof(uint16_t),
|
||||
"MAX_REGISTERS_PER_VALUE must match the register span of the widest encodable value");
|
||||
|
||||
ServerRegister *ModbusServer::find_containing_register_(uint32_t address) const {
|
||||
for (auto *server_register : this->server_registers_) {
|
||||
if (address >= server_register->address &&
|
||||
address < static_cast<uint32_t>(server_register->address) + server_register->register_count) {
|
||||
return server_register;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
modbus::ServerResponseStatus ModbusServer::on_modbus_read_registers(uint16_t start_address,
|
||||
uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) {
|
||||
@@ -34,68 +15,42 @@ modbus::ServerResponseStatus ModbusServer::on_modbus_read_registers(uint16_t sta
|
||||
"Received read holding/input registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%X.",
|
||||
this->address_, start_address, number_of_registers);
|
||||
|
||||
const uint32_t end_address = static_cast<uint32_t>(start_address) + number_of_registers;
|
||||
uint32_t current_address = start_address;
|
||||
while (current_address < end_address) {
|
||||
ServerRegister *server_register = this->find_containing_register_(current_address);
|
||||
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||
bool found = false;
|
||||
for (auto *server_register : this->server_registers_) {
|
||||
if (server_register->address == current_address) {
|
||||
if (!server_register->read_lambda) {
|
||||
break;
|
||||
}
|
||||
int64_t value = server_register->read_lambda();
|
||||
char value_buf[ServerRegister::FORMAT_VALUE_BUF_SIZE];
|
||||
ESP_LOGV(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
|
||||
server_register->address, static_cast<size_t>(server_register->value_type),
|
||||
server_register->register_count, server_register->format_value(value, value_buf, sizeof(value_buf)));
|
||||
|
||||
if (server_register == nullptr) {
|
||||
// Unregistered address: optionally answer with the courtesy default, otherwise reject.
|
||||
if (this->server_courtesy_response_.enabled &&
|
||||
current_address <= this->server_courtesy_response_.register_last_address) {
|
||||
ESP_LOGV(TAG, "No register at 0x%04X; returning courtesy default %" PRIu16 ".",
|
||||
static_cast<uint16_t>(current_address), this->server_courtesy_response_.register_value);
|
||||
registers.push_back(this->server_courtesy_response_.register_value);
|
||||
current_address += 1; // the courtesy default is always a single register
|
||||
continue;
|
||||
modbus::helpers::number_to_payload(registers, value, server_register->value_type);
|
||||
current_address += server_register->register_count;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "No register at 0x%04X and courtesy default not allowed. Sending exception response.",
|
||||
static_cast<uint16_t>(current_address));
|
||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
if (!server_register->read_lambda) {
|
||||
// Registered but not readable (write-only); don't mask it with the courtesy default.
|
||||
ESP_LOGW(TAG, "Register at 0x%04X is not readable. Sending exception response.", server_register->address);
|
||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||
if (!found) {
|
||||
if (this->server_courtesy_response_.enabled &&
|
||||
(current_address <= this->server_courtesy_response_.register_last_address)) {
|
||||
ESP_LOGV(TAG,
|
||||
"Could not match any register to address 0x%02X, but default allowed. "
|
||||
"Returning default value: %" PRIu16 ".",
|
||||
current_address, this->server_courtesy_response_.register_value);
|
||||
registers.push_back(this->server_courtesy_response_.register_value);
|
||||
current_address += 1; // Just increment by 1, as the default response is a single register
|
||||
} else {
|
||||
ESP_LOGW(TAG,
|
||||
"Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
|
||||
current_address);
|
||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
}
|
||||
|
||||
// A multi-register value is normally atomic: the request must start at its first register and cover all of
|
||||
// it. A value may opt in to partial reads, in which case the request may start inside it or stop short of
|
||||
// its end and we return only the covered words.
|
||||
const uint16_t value_offset = static_cast<uint16_t>(current_address - server_register->address);
|
||||
const uint16_t words_available = static_cast<uint16_t>(server_register->register_count - value_offset);
|
||||
const uint16_t words_wanted = static_cast<uint16_t>(end_address - current_address);
|
||||
const uint16_t take = words_available < words_wanted ? words_available : words_wanted;
|
||||
const bool clipped = value_offset != 0 || take != server_register->register_count;
|
||||
if (clipped && !server_register->allow_partial_read) {
|
||||
ESP_LOGW(TAG,
|
||||
"Read clips the multi-register value at 0x%04X, which does not allow partial reads. "
|
||||
"Sending exception response.",
|
||||
server_register->address);
|
||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
int64_t value = server_register->read_lambda();
|
||||
char value_buf[ServerRegister::FORMAT_VALUE_BUF_SIZE];
|
||||
ESP_LOGV(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
|
||||
server_register->address, static_cast<size_t>(server_register->value_type),
|
||||
server_register->register_count, server_register->format_value(value, value_buf, sizeof(value_buf)));
|
||||
|
||||
// Encode the whole value once (wire word order) and emit only the covered words. Slicing the encoded words
|
||||
// handles the reversed value types for free, since number_to_payload already emits in wire order.
|
||||
StaticVector<uint16_t, MAX_REGISTERS_PER_VALUE> value_words;
|
||||
modbus::helpers::number_to_payload(value_words, value, server_register->value_type);
|
||||
if (value_offset + take > value_words.size()) {
|
||||
// The value encoded to fewer words than its register span (e.g. a RAW register); treat as a device fault.
|
||||
ESP_LOGE(TAG, "Register at 0x%04X did not encode to %u registers", server_register->address,
|
||||
server_register->register_count);
|
||||
return ModbusExceptionCode::SERVICE_DEVICE_FAILURE;
|
||||
}
|
||||
for (uint16_t i = 0; i < take; i++) {
|
||||
registers.push_back(value_words[value_offset + i]);
|
||||
}
|
||||
current_address += take;
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
@@ -84,13 +84,9 @@ class ServerRegister {
|
||||
}
|
||||
}
|
||||
|
||||
void set_allow_partial_read(bool allow_partial_read) { this->allow_partial_read = allow_partial_read; }
|
||||
|
||||
uint16_t address{0};
|
||||
SensorValueType value_type{SensorValueType::RAW};
|
||||
uint8_t register_count{0};
|
||||
// When true, a read may cover only part of this multi-register value; otherwise it must read the whole value.
|
||||
bool allow_partial_read{false};
|
||||
ReadLambda read_lambda;
|
||||
WriteLambda write_lambda;
|
||||
};
|
||||
@@ -115,8 +111,6 @@ class ModbusServer : public Component, public modbus::ModbusServerDevice {
|
||||
ServerCourtesyResponse get_server_courtesy_response() const { return this->server_courtesy_response_; }
|
||||
|
||||
protected:
|
||||
/// Find the registered value whose register span contains address, or nullptr if none does.
|
||||
ServerRegister *find_containing_register_(uint32_t address) const;
|
||||
/// Collection of all server registers for this component
|
||||
std::vector<ServerRegister *> server_registers_{};
|
||||
/// Server courtesy response
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
namespace esphome::ssd1351_spi {
|
||||
|
||||
class SPISSD1351 : public ssd1351_base::SSD1351,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
class SPISSD1351 final : public ssd1351_base::SSD1351,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::st7567_i2c {
|
||||
|
||||
class I2CST7567 : public st7567_base::ST7567, public i2c::I2CDevice {
|
||||
class I2CST7567 final : public st7567_base::ST7567, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
namespace esphome::st7567_spi {
|
||||
|
||||
class SPIST7567 : public st7567_base::ST7567,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
class SPIST7567 final : public st7567_base::ST7567,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ const uint8_t CMD2_BKSEL = 0xFF;
|
||||
const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10};
|
||||
const uint8_t ST7701S_DELAY_FLAG = 0xFF;
|
||||
|
||||
class ST7701S : public display::Display,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
class ST7701S final : public display::Display,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
void update() override { this->do_update_(); }
|
||||
void setup() override;
|
||||
|
||||
@@ -31,9 +31,9 @@ enum ST7735Model {
|
||||
ST7735_INITR_18REDTAB = INITR_18REDTAB
|
||||
};
|
||||
|
||||
class ST7735 : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
class ST7735 final : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr,
|
||||
bool invert_colors);
|
||||
|
||||
@@ -106,9 +106,9 @@ static const uint8_t ST7789_MADCTL_GS = 0x01;
|
||||
|
||||
static const uint8_t ST7789_MADCTL_COLOR_ORDER = ST7789_MADCTL_BGR;
|
||||
|
||||
class ST7789V : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_20MHZ> {
|
||||
class ST7789V final : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_20MHZ> {
|
||||
public:
|
||||
void set_model_str(const char *model_str);
|
||||
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
|
||||
@@ -10,9 +10,9 @@ class ST7920;
|
||||
|
||||
using st7920_writer_t = display::DisplayWriter<ST7920>;
|
||||
|
||||
class ST7920 : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_200KHZ> {
|
||||
class ST7920 final : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
public:
|
||||
void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; }
|
||||
void set_height(uint16_t height) { this->height_ = height; }
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
namespace esphome::statsd {
|
||||
|
||||
class StatsdComponent : public PollingComponent {
|
||||
class StatsdComponent final : public PollingComponent {
|
||||
public:
|
||||
~StatsdComponent();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::status {
|
||||
|
||||
class StatusBinarySensor : public binary_sensor::BinarySensor, public PollingComponent {
|
||||
class StatusBinarySensor final : public binary_sensor::BinarySensor, public PollingComponent {
|
||||
public:
|
||||
void update() override;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::status_led {
|
||||
|
||||
class StatusLEDLightOutput : public light::LightOutput, public Component {
|
||||
class StatusLEDLightOutput final : public light::LightOutput, public Component {
|
||||
public:
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_output(output::BinaryOutput *output) { output_ = output; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::status_led {
|
||||
|
||||
class StatusLED : public Component {
|
||||
class StatusLED final : public Component {
|
||||
public:
|
||||
explicit StatusLED(GPIOPin *pin);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class Stepper {
|
||||
uint32_t last_step_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetTargetAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SetTargetAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetTargetAction(Stepper *parent) : parent_(parent) {}
|
||||
|
||||
@@ -49,7 +49,7 @@ template<typename... Ts> class SetTargetAction : public Action<Ts...> {
|
||||
Stepper *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReportPositionAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ReportPositionAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ReportPositionAction(Stepper *parent) : parent_(parent) {}
|
||||
|
||||
@@ -61,7 +61,7 @@ template<typename... Ts> class ReportPositionAction : public Action<Ts...> {
|
||||
Stepper *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetSpeedAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SetSpeedAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetSpeedAction(Stepper *parent) : parent_(parent) {}
|
||||
|
||||
@@ -77,7 +77,7 @@ template<typename... Ts> class SetSpeedAction : public Action<Ts...> {
|
||||
Stepper *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetAccelerationAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SetAccelerationAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetAccelerationAction(Stepper *parent) : parent_(parent) {}
|
||||
|
||||
@@ -92,7 +92,7 @@ template<typename... Ts> class SetAccelerationAction : public Action<Ts...> {
|
||||
Stepper *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDecelerationAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SetDecelerationAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetDecelerationAction(Stepper *parent) : parent_(parent) {}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
namespace esphome::sts3x {
|
||||
|
||||
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
|
||||
class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
class STS3XComponent final : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::stts22h {
|
||||
|
||||
class STTS22HComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
class STTS22HComponent final : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
@@ -11,7 +11,7 @@ enum SensorType {
|
||||
SUN_SENSOR_AZIMUTH,
|
||||
};
|
||||
|
||||
class SunSensor : public sensor::Sensor, public PollingComponent {
|
||||
class SunSensor final : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void set_parent(Sun *parent) { parent_ = parent; }
|
||||
void set_type(SensorType type) { type_ = type; }
|
||||
|
||||
@@ -51,7 +51,7 @@ struct HorizontalCoordinate {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class Sun {
|
||||
class Sun final {
|
||||
public:
|
||||
void set_time(time::RealTimeClock *time) { time_ = time; }
|
||||
time::RealTimeClock *get_time() const { return time_; }
|
||||
@@ -78,7 +78,7 @@ class Sun {
|
||||
internal::GeoLocation location_;
|
||||
};
|
||||
|
||||
class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Sun> {
|
||||
class SunTrigger final : public Trigger<>, public PollingComponent, public Parented<Sun> {
|
||||
public:
|
||||
SunTrigger() : PollingComponent(60000) {}
|
||||
|
||||
@@ -109,7 +109,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Su
|
||||
double elevation_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SunCondition : public Condition<Ts...>, public Parented<Sun> {
|
||||
template<typename... Ts> class SunCondition final : public Condition<Ts...>, public Parented<Sun> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(double, elevation);
|
||||
void set_above(bool above) { above_ = above; }
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::sun {
|
||||
|
||||
class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||
class SunTextSensor final : public text_sensor::TextSensor, public PollingComponent {
|
||||
public:
|
||||
void set_parent(Sun *parent) { parent_ = parent; }
|
||||
void set_elevation(double elevation) { elevation_ = elevation; }
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace esphome::sun_gtil2 {
|
||||
|
||||
class SunGTIL2 : public Component, public uart::UARTDevice {
|
||||
class SunGTIL2 final : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||
void setup() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
||||
template<typename... Ts> class TurnOnAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit TurnOnAction(Switch *a_switch) : switch_(a_switch) {}
|
||||
|
||||
@@ -16,7 +16,7 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
||||
Switch *switch_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
||||
template<typename... Ts> class TurnOffAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit TurnOffAction(Switch *a_switch) : switch_(a_switch) {}
|
||||
|
||||
@@ -26,7 +26,7 @@ template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
||||
Switch *switch_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ToggleAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ToggleAction(Switch *a_switch) : switch_(a_switch) {}
|
||||
|
||||
@@ -36,7 +36,7 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
Switch *switch_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ControlAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Switch *a_switch) : switch_(a_switch) {}
|
||||
|
||||
@@ -53,7 +53,7 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
Switch *switch_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SwitchCondition : public Condition<Ts...> {
|
||||
template<typename... Ts> class SwitchCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
SwitchCondition(Switch *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
|
||||
@@ -63,14 +63,14 @@ template<typename... Ts> class SwitchCondition : public Condition<Ts...> {
|
||||
bool state_;
|
||||
};
|
||||
|
||||
class SwitchStateTrigger : public Trigger<bool> {
|
||||
class SwitchStateTrigger final : public Trigger<bool> {
|
||||
public:
|
||||
SwitchStateTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class SwitchTurnOnTrigger : public Trigger<> {
|
||||
class SwitchTurnOnTrigger final : public Trigger<> {
|
||||
public:
|
||||
SwitchTurnOnTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) {
|
||||
@@ -81,7 +81,7 @@ class SwitchTurnOnTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
class SwitchTurnOffTrigger : public Trigger<> {
|
||||
class SwitchTurnOffTrigger final : public Trigger<> {
|
||||
public:
|
||||
SwitchTurnOffTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) {
|
||||
@@ -92,7 +92,7 @@ class SwitchTurnOffTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SwitchPublishAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SwitchPublishAction final : public Action<Ts...> {
|
||||
public:
|
||||
SwitchPublishAction(Switch *a_switch) : switch_(a_switch) {}
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
class SwitchBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
void set_source(Switch *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
namespace esphome::sx126x {
|
||||
|
||||
template<typename... Ts> class RunImageCalAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class RunImageCalAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->run_image_cal(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class SendPacketAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
this->data_.func = func;
|
||||
@@ -43,23 +43,23 @@ template<typename... Ts> class SendPacketAction : public Action<Ts...>, public P
|
||||
} data_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeTxAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class SetModeTxAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_tx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeRxAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class SetModeRxAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_rx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeSleepAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class SetModeSleepAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, cold)
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_sleep(this->cold_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeStandbyAction : public Action<Ts...>, public Parented<SX126x> {
|
||||
template<typename... Ts> class SetModeStandbyAction final : public Action<Ts...>, public Parented<SX126x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_standby(STDBY_XOSC); }
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::sx126x {
|
||||
|
||||
class SX126xTransport : public packet_transport::PacketTransport, public Parented<SX126x>, public SX126xListener {
|
||||
class SX126xTransport final : public packet_transport::PacketTransport, public Parented<SX126x>, public SX126xListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) override;
|
||||
|
||||
@@ -53,9 +53,9 @@ class SX126xListener {
|
||||
virtual void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) = 0;
|
||||
};
|
||||
|
||||
class SX126x : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
class SX126x final : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
size_t get_max_packet_size();
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
namespace esphome::sx127x {
|
||||
|
||||
template<typename... Ts> class RunImageCalAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class RunImageCalAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->run_image_cal(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class SendPacketAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
this->data_.func = func;
|
||||
@@ -43,22 +43,22 @@ template<typename... Ts> class SendPacketAction : public Action<Ts...>, public P
|
||||
} data_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeTxAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class SetModeTxAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_tx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeRxAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class SetModeRxAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_rx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeSleepAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class SetModeSleepAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_sleep(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModeStandbyAction : public Action<Ts...>, public Parented<SX127x> {
|
||||
template<typename... Ts> class SetModeStandbyAction final : public Action<Ts...>, public Parented<SX127x> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_mode_standby(); }
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::sx127x {
|
||||
|
||||
class SX127xTransport : public packet_transport::PacketTransport, public Parented<SX127x>, public SX127xListener {
|
||||
class SX127xTransport final : public packet_transport::PacketTransport, public Parented<SX127x>, public SX127xListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) override;
|
||||
|
||||
@@ -41,9 +41,9 @@ class SX127xListener {
|
||||
virtual void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) = 0;
|
||||
};
|
||||
|
||||
class SX127x : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
class SX127x final : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
size_t get_max_packet_size();
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::sx1509 {
|
||||
|
||||
class SX1509BinarySensor : public sx1509::SX1509Processor, public binary_sensor::BinarySensor {
|
||||
class SX1509BinarySensor final : public sx1509::SX1509Processor, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void set_row_col(uint8_t row, uint8_t col) { this->key_ = (1 << (col + 8)) | (1 << row); }
|
||||
void process(uint16_t data) override { this->publish_state(static_cast<bool>(data == key_)); }
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace esphome::sx1509 {
|
||||
|
||||
class SX1509Component;
|
||||
|
||||
class SX1509FloatOutputChannel : public output::FloatOutput, public Component {
|
||||
class SX1509FloatOutputChannel final : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void set_parent(SX1509Component *parent) { this->parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
|
||||
@@ -28,12 +28,12 @@ class SX1509Processor {
|
||||
virtual void process(uint16_t data){};
|
||||
};
|
||||
|
||||
class SX1509KeyTrigger : public Trigger<uint8_t> {};
|
||||
class SX1509KeyTrigger final : public Trigger<uint8_t> {};
|
||||
|
||||
class SX1509Component : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint16_t, 16>,
|
||||
public key_provider::KeyProvider {
|
||||
class SX1509Component final : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint16_t, 16>,
|
||||
public key_provider::KeyProvider {
|
||||
public:
|
||||
SX1509Component() = default;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace esphome::sx1509 {
|
||||
|
||||
class SX1509Component;
|
||||
|
||||
class SX1509GPIOPin : public GPIOPin {
|
||||
class SX1509GPIOPin final : public GPIOPin {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
template<uint8_t REG, uint8_t SHIFT, uint8_t MASK, uint8_t TRUE_VALUE>
|
||||
class StatusBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
class StatusBinarySensor final : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t value = (data.registers[REG] >> SHIFT) & MASK;
|
||||
@@ -24,7 +24,7 @@ class InverseStatusBinarySensor : public SY6970Listener, public binary_sensor::B
|
||||
};
|
||||
|
||||
// Custom binary sensor for charging (true when pre-charge or fast charge)
|
||||
class SY6970ChargingBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
class SY6970ChargingBinarySensor final : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t chrg_stat = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
||||
|
||||
@@ -34,7 +34,7 @@ using SY6970SystemVoltageSensor = VoltageSensor<SY6970_REG_VINDPM_STATUS, 0x7F,
|
||||
using SY6970ChargeCurrentSensor = CurrentSensor<SY6970_REG_CHARGE_CURRENT_MONITOR, 0x7F, 0, CHG_CURRENT_STEP_MA>;
|
||||
|
||||
// Precharge current sensor needs special handling (bit shift)
|
||||
class SY6970PrechargeCurrentSensor : public SY6970Listener, public sensor::Sensor {
|
||||
class SY6970PrechargeCurrentSensor final : public SY6970Listener, public sensor::Sensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t iprechg = (data.registers[SY6970_REG_PRECHARGE_CURRENT] >> 4) & 0x0F;
|
||||
|
||||
@@ -73,7 +73,7 @@ class SY6970Listener {
|
||||
virtual void on_data(const SY6970Data &data) = 0;
|
||||
};
|
||||
|
||||
class SY6970Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class SY6970Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
SY6970Component(bool led_enabled, uint16_t input_current_limit, uint16_t charge_voltage, uint16_t charge_current,
|
||||
uint16_t precharge_current, bool charge_enabled, bool enable_adc)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
// Bus status text sensor
|
||||
class SY6970BusStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
class SY6970BusStatusTextSensor final : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 5) & 0x07;
|
||||
@@ -40,7 +40,7 @@ class SY6970BusStatusTextSensor : public SY6970Listener, public text_sensor::Tex
|
||||
};
|
||||
|
||||
// Charge status text sensor
|
||||
class SY6970ChargeStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
class SY6970ChargeStatusTextSensor final : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
||||
@@ -66,7 +66,7 @@ class SY6970ChargeStatusTextSensor : public SY6970Listener, public text_sensor::
|
||||
};
|
||||
|
||||
// NTC status text sensor
|
||||
class SY6970NtcStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
class SY6970NtcStatusTextSensor final : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = data.registers[SY6970_REG_FAULT] & 0x07;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#ifdef USE_NETWORK
|
||||
namespace esphome::syslog {
|
||||
class Syslog : public Component, public Parented<udp::UDPComponent> {
|
||||
class Syslog final : public Component, public Parented<udp::UDPComponent> {
|
||||
public:
|
||||
Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {}
|
||||
void setup() override;
|
||||
|
||||
@@ -19,7 +19,7 @@ enum class T6615Command : uint8_t {
|
||||
SET_ELEVATION,
|
||||
};
|
||||
|
||||
class T6615Component : public PollingComponent, public uart::UARTDevice {
|
||||
class T6615Component final : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::tc74 {
|
||||
|
||||
class TC74Component : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
||||
class TC74Component final : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
||||
public:
|
||||
/// Setup the sensor and check connection.
|
||||
void setup() override;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace esphome::tca9548a {
|
||||
static const uint8_t TCA9548A_DISABLE_CHANNELS_COMMAND = 0x00;
|
||||
|
||||
class TCA9548AComponent;
|
||||
class TCA9548AChannel : public i2c::I2CBus {
|
||||
class TCA9548AChannel final : public i2c::I2CBus {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
void set_parent(TCA9548AComponent *parent) { parent_ = parent; }
|
||||
@@ -21,7 +21,7 @@ class TCA9548AChannel : public i2c::I2CBus {
|
||||
TCA9548AComponent *parent_;
|
||||
};
|
||||
|
||||
class TCA9548AComponent : public Component, public i2c::I2CDevice {
|
||||
class TCA9548AComponent final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
namespace esphome::tca9555 {
|
||||
|
||||
class TCA9555Component : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint8_t, 16> {
|
||||
class TCA9555Component final : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint8_t, 16> {
|
||||
public:
|
||||
TCA9555Component() = default;
|
||||
|
||||
@@ -47,7 +47,7 @@ class TCA9555Component : public Component,
|
||||
};
|
||||
|
||||
/// Helper class to expose a TCA9555 pin as an internal input GPIO pin.
|
||||
class TCA9555GPIOPin : public GPIOPin, public Parented<TCA9555Component> {
|
||||
class TCA9555GPIOPin final : public GPIOPin, public Parented<TCA9555Component> {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace esphome::tcl112 {
|
||||
const float TCL112_TEMP_MAX = 31.0;
|
||||
const float TCL112_TEMP_MIN = 16.0;
|
||||
|
||||
class Tcl112Climate : public climate_ir::ClimateIR {
|
||||
class Tcl112Climate final : public climate_ir::ClimateIR {
|
||||
public:
|
||||
Tcl112Climate()
|
||||
: climate_ir::ClimateIR(TCL112_TEMP_MIN, TCL112_TEMP_MAX, .5f, true, true,
|
||||
|
||||
@@ -35,7 +35,7 @@ enum TCS34725Gain {
|
||||
TCS34725_GAIN_60X = 0x03,
|
||||
};
|
||||
|
||||
class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class TCS34725Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_integration_time(TCS34725IntegrationTime integration_time);
|
||||
void set_gain(TCS34725Gain gain);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace esphome::tee501 {
|
||||
|
||||
/// This class implements support for the tee501 of temperature i2c sensors.
|
||||
class TEE501Component : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
class TEE501Component final : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::teleinfo {
|
||||
|
||||
class TeleInfoSensor : public TeleInfoListener, public sensor::Sensor, public Component {
|
||||
class TeleInfoSensor final : public TeleInfoListener, public sensor::Sensor, public Component {
|
||||
public:
|
||||
TeleInfoSensor(const char *tag);
|
||||
void publish_val(const std::string &val) override;
|
||||
|
||||
@@ -20,7 +20,7 @@ class TeleInfoListener {
|
||||
std::string tag;
|
||||
virtual void publish_val(const std::string &val){};
|
||||
};
|
||||
class TeleInfo : public PollingComponent, public uart::UARTDevice {
|
||||
class TeleInfo final : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
TeleInfo(bool historical_mode);
|
||||
void register_teleinfo_listener(TeleInfoListener *listener);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::teleinfo {
|
||||
class TeleInfoTextSensor : public TeleInfoListener, public text_sensor::TextSensor, public Component {
|
||||
class TeleInfoTextSensor final : public TeleInfoListener, public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
TeleInfoTextSensor(const char *tag);
|
||||
void publish_val(const std::string &val) override;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace esphome::tem3200 {
|
||||
|
||||
/// This class implements support for the tem3200 pressure and temperature i2c sensors.
|
||||
class TEM3200Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class TEM3200Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_raw_pressure_sensor(sensor::Sensor *raw_pressure_sensor) {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ puremagic==2.2.0
|
||||
ruamel.yaml==0.19.1 # dashboard_import
|
||||
ruamel.yaml.clib==0.2.15 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
pillow==12.3.0
|
||||
pillow==12.2.0
|
||||
resvg-py==0.3.3
|
||||
freetype-py==2.5.1
|
||||
jinja2==3.1.6
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
"""Tests for modbus_server configuration validation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.modbus_server import (
|
||||
SERVER_SENSOR_VALUE_TYPE,
|
||||
_validate_no_overlapping_registers,
|
||||
_validate_register_ranges,
|
||||
)
|
||||
from esphome.components.modbus_server.const import CONF_REGISTERS, CONF_VALUE_TYPE
|
||||
from esphome.const import CONF_ADDRESS
|
||||
|
||||
|
||||
def _config(registers: list[tuple[int, str]]) -> dict:
|
||||
return {
|
||||
CONF_REGISTERS: [
|
||||
{CONF_ADDRESS: address, CONF_VALUE_TYPE: value_type}
|
||||
for address, value_type in registers
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_non_overlapping_registers_pass() -> None:
|
||||
# Values that tile the address space without gaps or overlaps are accepted.
|
||||
config = _config([(0x00, "U_WORD"), (0x01, "U_DWORD"), (0x03, "U_WORD")])
|
||||
assert _validate_no_overlapping_registers(config) is config
|
||||
|
||||
|
||||
def test_registers_with_gaps_pass() -> None:
|
||||
config = _config([(0x00, "U_WORD"), (0x05, "U_QWORD"), (0x20, "U_WORD")])
|
||||
assert _validate_no_overlapping_registers(config) is config
|
||||
|
||||
|
||||
def test_no_registers_pass() -> None:
|
||||
assert _validate_no_overlapping_registers({}) == {}
|
||||
|
||||
|
||||
def test_duplicate_address_rejected() -> None:
|
||||
config = _config([(0x10, "U_WORD"), (0x10, "U_WORD")])
|
||||
with pytest.raises(cv.Invalid, match="overlaps"):
|
||||
_validate_no_overlapping_registers(config)
|
||||
|
||||
|
||||
def test_multi_register_value_overlapping_neighbour_rejected() -> None:
|
||||
# U_DWORD at 0x10 occupies 0x10 and 0x11; a U_WORD at 0x11 collides with its low word.
|
||||
config = _config([(0x10, "U_DWORD"), (0x11, "U_WORD")])
|
||||
with pytest.raises(cv.Invalid, match="overlaps"):
|
||||
_validate_no_overlapping_registers(config)
|
||||
|
||||
|
||||
def test_overlap_detected_regardless_of_order() -> None:
|
||||
# The U_DWORD at 0x10 covers 0x10-0x11 and overlaps the U_WORD at 0x11 even when declared after it.
|
||||
config = _config([(0x11, "U_WORD"), (0x10, "U_DWORD")])
|
||||
with pytest.raises(cv.Invalid, match="overlaps"):
|
||||
_validate_no_overlapping_registers(config)
|
||||
|
||||
|
||||
def test_register_span_within_address_space_pass() -> None:
|
||||
# A value whose span ends exactly at 0xFFFF is fine (U_QWORD at 0xFFFC covers 0xFFFC-0xFFFF).
|
||||
config = _config([(0xFFFF, "U_WORD"), (0xFFFC, "U_QWORD")])
|
||||
assert _validate_register_ranges(config) is config
|
||||
|
||||
|
||||
def test_register_span_past_end_rejected() -> None:
|
||||
# U_QWORD at 0xFFFE would need 0xFFFE-0x10001, running off the 16-bit address space.
|
||||
config = _config([(0xFFFE, "U_QWORD")])
|
||||
with pytest.raises(cv.Invalid, match="past the end"):
|
||||
_validate_register_ranges(config)
|
||||
|
||||
|
||||
def test_multi_register_value_at_last_address_rejected() -> None:
|
||||
# A U_DWORD at 0xFFFF needs a second register at 0x10000, which does not exist.
|
||||
config = _config([(0xFFFF, "U_DWORD")])
|
||||
with pytest.raises(cv.Invalid, match="past the end"):
|
||||
_validate_register_ranges(config)
|
||||
|
||||
|
||||
def test_raw_value_type_rejected() -> None:
|
||||
# RAW has no numeric encoding, so it is not offered as a server register type.
|
||||
validator = cv.enum(SERVER_SENSOR_VALUE_TYPE)
|
||||
with pytest.raises(cv.Invalid):
|
||||
validator("RAW")
|
||||
assert validator("U_WORD") == "U_WORD"
|
||||
@@ -18,7 +18,6 @@ modbus_server:
|
||||
registers:
|
||||
- address: 0x9
|
||||
value_type: S_DWORD
|
||||
allow_partial_read: true
|
||||
read_lambda: |-
|
||||
return 31;
|
||||
write_lambda: |-
|
||||
|
||||
@@ -121,165 +121,4 @@ TEST(ModbusServerWrite, CallbackFailureIsServiceDeviceFailure) {
|
||||
EXPECT_TRUE(first_written); // pre-validation passed, so the first write applied before the failure
|
||||
}
|
||||
|
||||
// --- on_modbus_read_registers --------------------------------------------------
|
||||
|
||||
TEST(ModbusServerRead, SingleWordSucceeds) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_WORD, 1);
|
||||
reg.read_lambda = []() -> int64_t { return 0x1234; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
}
|
||||
|
||||
TEST(ModbusServerRead, DwordReturnsTwoWordsHighFirst) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_DWORD, 2);
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
EXPECT_EQ(out[1], 0x5678);
|
||||
}
|
||||
|
||||
// Starting inside a multi-register value is rejected with ILLEGAL_DATA_ADDRESS -- not masked by the courtesy
|
||||
// default -- and the read_lambda is never invoked.
|
||||
TEST(ModbusServerRead, StartInsideValueRejected) {
|
||||
ModbusServer server;
|
||||
bool read_called = false;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2); // occupies 0x0010 and 0x0011
|
||||
reg.read_lambda = [&read_called]() -> int64_t {
|
||||
read_called = true;
|
||||
return 0;
|
||||
};
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
EXPECT_FALSE(read_called);
|
||||
}
|
||||
|
||||
// A read that stops short of a value's end clips it -> ILLEGAL_DATA_ADDRESS, and the read_lambda is not invoked.
|
||||
TEST(ModbusServerRead, ClippedTailRejected) {
|
||||
ModbusServer server;
|
||||
bool read_called = false;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_DWORD, 2);
|
||||
reg.read_lambda = [&read_called]() -> int64_t {
|
||||
read_called = true;
|
||||
return 0;
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
EXPECT_FALSE(read_called);
|
||||
}
|
||||
|
||||
// A write-only register (no read_lambda) is not readable -> ILLEGAL_DATA_ADDRESS, not a courtesy default.
|
||||
TEST(ModbusServerRead, WriteOnlyRegisterRejected) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_WORD, 1); // no read_lambda set
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// An unregistered address with courtesy enabled returns the default value for each cell.
|
||||
TEST(ModbusServerRead, CourtesyDefaultForUnregistered) {
|
||||
ModbusServer server;
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0xABCD);
|
||||
EXPECT_EQ(out[1], 0xABCD);
|
||||
}
|
||||
|
||||
// An unregistered address with courtesy disabled is rejected.
|
||||
TEST(ModbusServerRead, UnregisteredRejectedWithoutCourtesy) {
|
||||
ModbusServer server;
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// --- partial reads (opt-in) ----------------------------------------------------
|
||||
|
||||
// With allow_partial_read, reading only the first register of a DWORD returns its high word.
|
||||
TEST(ModbusServerRead, PartialReadHighWord) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0010, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
}
|
||||
|
||||
// With allow_partial_read, starting at the interior cell returns the low word.
|
||||
TEST(ModbusServerRead, PartialReadLowWordFromInterior) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x5678);
|
||||
}
|
||||
|
||||
// Slicing is in wire order, so a reversed value type partials correctly: U_DWORD_R emits the low word
|
||||
// first, so 0x0010 holds 0x5678 and 0x0011 holds 0x1234.
|
||||
TEST(ModbusServerRead, PartialReadReversedType) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD_R, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues first;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0010, 1, first).has_value());
|
||||
ASSERT_EQ(first.size(), 1u);
|
||||
EXPECT_EQ(first[0], 0x5678);
|
||||
|
||||
RegisterValues second;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0011, 1, second).has_value());
|
||||
ASSERT_EQ(second.size(), 1u);
|
||||
EXPECT_EQ(second[0], 0x1234);
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus_server
|
||||
|
||||
Reference in New Issue
Block a user