mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[modbus] Rename core enums to EntityType, FunctionCode and ExceptionCode (#17844)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5e2d428e69
commit
9ac4039c01
@@ -3,33 +3,34 @@ import esphome.codegen as cg
|
|||||||
modbus_ns = cg.esphome_ns.namespace("modbus")
|
modbus_ns = cg.esphome_ns.namespace("modbus")
|
||||||
modbus_helpers_ns = modbus_ns.namespace("helpers")
|
modbus_helpers_ns = modbus_ns.namespace("helpers")
|
||||||
|
|
||||||
ModbusFunctionCode_ns = modbus_ns.namespace("ModbusFunctionCode")
|
FunctionCode_ns = modbus_ns.namespace("FunctionCode")
|
||||||
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
|
FunctionCode = FunctionCode_ns.enum("FunctionCode")
|
||||||
|
|
||||||
MODBUS_FUNCTION_CODE = {
|
MODBUS_FUNCTION_CODE = {
|
||||||
"read_coils": ModbusFunctionCode.READ_COILS,
|
"read_coils": FunctionCode.READ_COILS,
|
||||||
"read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS,
|
"read_discrete_inputs": FunctionCode.READ_DISCRETE_INPUTS,
|
||||||
"read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS,
|
"read_holding_registers": FunctionCode.READ_HOLDING_REGISTERS,
|
||||||
"read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS,
|
"read_input_registers": FunctionCode.READ_INPUT_REGISTERS,
|
||||||
"write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL,
|
"write_single_coil": FunctionCode.WRITE_SINGLE_COIL,
|
||||||
"write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER,
|
"write_single_register": FunctionCode.WRITE_SINGLE_REGISTER,
|
||||||
"write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS,
|
"write_multiple_coils": FunctionCode.WRITE_MULTIPLE_COILS,
|
||||||
"write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS,
|
"write_multiple_registers": FunctionCode.WRITE_MULTIPLE_REGISTERS,
|
||||||
}
|
}
|
||||||
|
|
||||||
ModbusRegisterType_ns = modbus_ns.namespace("ModbusRegisterType")
|
EntityType_ns = modbus_ns.namespace("EntityType")
|
||||||
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
|
EntityType = EntityType_ns.enum("EntityType")
|
||||||
|
|
||||||
MODBUS_WRITE_REGISTER_TYPE = {
|
MODBUS_WRITE_REGISTER_TYPE = {
|
||||||
"custom": ModbusRegisterType.CUSTOM,
|
"custom": EntityType.CUSTOM,
|
||||||
"coil": ModbusRegisterType.COIL,
|
"coil": EntityType.COIL,
|
||||||
"holding": ModbusRegisterType.HOLDING,
|
"holding": EntityType.HOLDING,
|
||||||
}
|
}
|
||||||
|
|
||||||
MODBUS_REGISTER_TYPE = {
|
MODBUS_REGISTER_TYPE = {
|
||||||
**MODBUS_WRITE_REGISTER_TYPE,
|
**MODBUS_WRITE_REGISTER_TYPE,
|
||||||
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
|
"discrete_input": EntityType.DISCRETE_INPUT,
|
||||||
"read": ModbusRegisterType.INPUT_REGISTER,
|
"read": EntityType.INPUT_REGISTER,
|
||||||
|
"input": EntityType.INPUT_REGISTER,
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
|
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ void ModbusClientHub::process_modbus_server_frame(uint8_t address, std::span<con
|
|||||||
"Error function code: 0x%X exception: %" PRIu8 ", address: %" PRIu8 ", %" PRIu32 "ms after last send",
|
"Error function code: 0x%X exception: %" PRIu8 ", address: %" PRIu8 ", %" PRIu32 "ms after last send",
|
||||||
function_code, exception, address, this->last_modbus_byte_ - this->last_send_);
|
function_code, exception, address, this->last_modbus_byte_ - this->last_send_);
|
||||||
if (device)
|
if (device)
|
||||||
device->on_error(request_pdu, static_cast<ModbusExceptionCode>(exception));
|
device->on_error(request_pdu, static_cast<ExceptionCode>(exception));
|
||||||
|
|
||||||
} else if (device) { // Not an error response
|
} else if (device) { // Not an error response
|
||||||
device->on_response(request_pdu, pdu);
|
device->on_response(request_pdu, pdu);
|
||||||
@@ -354,7 +354,7 @@ bool ModbusServerHub::check_register_range_(uint8_t address, uint8_t function_co
|
|||||||
if ((uint32_t) start_address + number_of_registers > 0x10000u) {
|
if ((uint32_t) start_address + number_of_registers > 0x10000u) {
|
||||||
ESP_LOGW(TAG, "Register address out of range - start: %" PRIu16 " num: %" PRIu16, start_address,
|
ESP_LOGW(TAG, "Register address out of range - start: %" PRIu16 " num: %" PRIu16, start_address,
|
||||||
number_of_registers);
|
number_of_registers);
|
||||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
this->send_exception_(address, function_code, ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -373,22 +373,22 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
const uint8_t *response_data = response_buffer;
|
const uint8_t *response_data = response_buffer;
|
||||||
uint16_t response_len = 0;
|
uint16_t response_len = 0;
|
||||||
|
|
||||||
switch (static_cast<ModbusFunctionCode>(function_code)) {
|
switch (static_cast<FunctionCode>(function_code)) {
|
||||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
case FunctionCode::READ_HOLDING_REGISTERS:
|
||||||
case ModbusFunctionCode::READ_INPUT_REGISTERS: {
|
case FunctionCode::READ_INPUT_REGISTERS: {
|
||||||
// PDU data: start address(2) + quantity(2).
|
// PDU data: start address(2) + quantity(2).
|
||||||
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
||||||
uint16_t number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
uint16_t number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
||||||
if (number_of_registers == 0 || number_of_registers > MAX_NUM_OF_REGISTERS_TO_READ) {
|
if (number_of_registers == 0 || number_of_registers > MAX_NUM_OF_REGISTERS_TO_READ) {
|
||||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16, number_of_registers);
|
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16, number_of_registers);
|
||||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
this->send_exception_(address, function_code, ExceptionCode::ILLEGAL_DATA_VALUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RegisterValues registers;
|
RegisterValues registers;
|
||||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_HOLDING_REGISTERS) {
|
if (static_cast<FunctionCode>(function_code) == FunctionCode::READ_HOLDING_REGISTERS) {
|
||||||
status = device->on_read_holding_registers(start_address, number_of_registers, registers);
|
status = device->on_read_holding_registers(start_address, number_of_registers, registers);
|
||||||
} else {
|
} else {
|
||||||
status = device->on_read_input_registers(start_address, number_of_registers, registers);
|
status = device->on_read_input_registers(start_address, number_of_registers, registers);
|
||||||
@@ -403,7 +403,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
|
|
||||||
if (registers.size() != number_of_registers) {
|
if (registers.size() != number_of_registers) {
|
||||||
ESP_LOGE(TAG, "Incorrect response %" PRIu16 " requested, %zu returned", number_of_registers, registers.size());
|
ESP_LOGE(TAG, "Incorrect response %" PRIu16 " requested, %zu returned", number_of_registers, registers.size());
|
||||||
this->send_exception_(address, function_code, ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
this->send_exception_(address, function_code, ExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,8 +415,8 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_REGISTER:
|
case FunctionCode::WRITE_SINGLE_REGISTER:
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS: {
|
case FunctionCode::WRITE_MULTIPLE_REGISTERS: {
|
||||||
// PDU data: start address(2) [+ quantity(2) + byte count(1)] + register values.
|
// PDU data: start address(2) [+ quantity(2) + byte count(1)] + register values.
|
||||||
// A single-register write always targets one register; for a multiple-register write the
|
// A single-register write always targets one register; for a multiple-register write the
|
||||||
// quantity is in the frame and its byte count must equal quantity * 2. The register values are
|
// quantity is in the frame and its byte count must equal quantity * 2. The register values are
|
||||||
@@ -424,7 +424,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
||||||
uint16_t number_of_registers = 1;
|
uint16_t number_of_registers = 1;
|
||||||
uint16_t values_offset = 2; // single write: values follow the 2-byte start address
|
uint16_t values_offset = 2; // single write: values follow the 2-byte start address
|
||||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
if (static_cast<FunctionCode>(function_code) == FunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
||||||
uint8_t number_of_bytes = helpers::get_data<uint8_t>(data, 4);
|
uint8_t number_of_bytes = helpers::get_data<uint8_t>(data, 4);
|
||||||
values_offset = 5; // multiple write: values follow start address(2) + quantity(2) + byte count(1)
|
values_offset = 5; // multiple write: values follow start address(2) + quantity(2) + byte count(1)
|
||||||
@@ -432,7 +432,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
number_of_registers * 2 != number_of_bytes) {
|
number_of_registers * 2 != number_of_bytes) {
|
||||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16 " or bytes %" PRIu8, number_of_registers,
|
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16 " or bytes %" PRIu8, number_of_registers,
|
||||||
number_of_bytes);
|
number_of_bytes);
|
||||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
this->send_exception_(address, function_code, ExceptionCode::ILLEGAL_DATA_VALUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
||||||
@@ -451,7 +451,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(TAG, "Unsupported function code %" PRIu8, function_code);
|
ESP_LOGW(TAG, "Unsupported function code %" PRIu8, function_code);
|
||||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
this->send_exception_(address, function_code, ExceptionCode::ILLEGAL_FUNCTION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (status.has_value()) {
|
if (status.has_value()) {
|
||||||
@@ -563,7 +563,7 @@ void ModbusServerHub::send_response_(uint8_t address, uint8_t function_code, con
|
|||||||
this->send_raw_(raw_frame, payload_len + 2);
|
this->send_raw_(raw_frame, payload_len + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusServerHub::send_exception_(uint8_t address, uint8_t function_code, ModbusExceptionCode exception_code) {
|
void ModbusServerHub::send_exception_(uint8_t address, uint8_t function_code, ExceptionCode exception_code) {
|
||||||
uint8_t raw_frame[3];
|
uint8_t raw_frame[3];
|
||||||
raw_frame[0] = address;
|
raw_frame[0] = address;
|
||||||
raw_frame[1] = function_code | FUNCTION_CODE_EXCEPTION_MASK;
|
raw_frame[1] = function_code | FUNCTION_CODE_EXCEPTION_MASK;
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ class ModbusClientHub : public Modbus {
|
|||||||
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
|
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
|
||||||
uint8_t payload_len = 0, const uint8_t *payload = nullptr, ModbusClientDevice *device = nullptr) {
|
uint8_t payload_len = 0, const uint8_t *payload = nullptr, ModbusClientDevice *device = nullptr) {
|
||||||
this->send_pdu(address,
|
this->send_pdu(address,
|
||||||
helpers::create_client_pdu((ModbusFunctionCode) function_code, start_address, number_of_entities,
|
helpers::create_client_pdu((FunctionCode) function_code, start_address, number_of_entities, payload,
|
||||||
payload, payload_len),
|
payload_len),
|
||||||
device);
|
device);
|
||||||
};
|
};
|
||||||
void send_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device = nullptr) {
|
void send_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device = nullptr) {
|
||||||
@@ -154,7 +154,7 @@ class ModbusServerHub : public Modbus {
|
|||||||
bool check_register_range_(uint8_t address, uint8_t function_code, uint16_t start_address,
|
bool check_register_range_(uint8_t address, uint8_t function_code, uint16_t start_address,
|
||||||
uint16_t number_of_registers);
|
uint16_t number_of_registers);
|
||||||
void send_raw_(const uint8_t *payload, uint16_t len);
|
void send_raw_(const uint8_t *payload, uint16_t len);
|
||||||
void send_exception_(uint8_t address, uint8_t function_code, ModbusExceptionCode exception_code);
|
void send_exception_(uint8_t address, uint8_t function_code, ExceptionCode exception_code);
|
||||||
void send_response_(uint8_t address, uint8_t function_code, const uint8_t *payload, uint16_t payload_len);
|
void send_response_(uint8_t address, uint8_t function_code, const uint8_t *payload, uint16_t payload_len);
|
||||||
uint8_t expecting_peer_response_{0};
|
uint8_t expecting_peer_response_{0};
|
||||||
std::vector<ModbusServerDevice *> devices_;
|
std::vector<ModbusServerDevice *> devices_;
|
||||||
@@ -184,7 +184,7 @@ class ModbusClientDevice {
|
|||||||
/// if they must outlive it. Slice the payload out of the response with helpers::server_pdu_payload().
|
/// if they must outlive it. Slice the payload out of the response with helpers::server_pdu_payload().
|
||||||
virtual void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {}
|
virtual void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {}
|
||||||
/// Called with the request PDU and the modbus exception code decoded from the error response.
|
/// Called with the request PDU and the modbus exception code decoded from the error response.
|
||||||
virtual void on_error(std::span<const uint8_t> request_pdu, ModbusExceptionCode exception_code) {}
|
virtual void on_error(std::span<const uint8_t> request_pdu, ExceptionCode exception_code) {}
|
||||||
// The on_modbus_* names are signature-identical renames, so the new defaults forward to the old
|
// The on_modbus_* names are signature-identical renames, so the new defaults forward to the old
|
||||||
// virtuals: external devices overriding the old names keep working through the deprecation window.
|
// virtuals: external devices overriding the old names keep working through the deprecation window.
|
||||||
// Remove the forwards together with the deprecated names.
|
// Remove the forwards together with the deprecated names.
|
||||||
@@ -211,10 +211,10 @@ class ModbusClientDevice {
|
|||||||
virtual bool on_modbus_no_response() { return false; }
|
virtual bool on_modbus_no_response() { return false; }
|
||||||
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
||||||
const uint8_t *payload = nullptr) {
|
const uint8_t *payload = nullptr) {
|
||||||
this->parent_->send_pdu(this->address_,
|
this->parent_->send_pdu(
|
||||||
helpers::create_client_pdu((ModbusFunctionCode) function, start_address, number_of_entities,
|
this->address_,
|
||||||
payload, payload_len),
|
helpers::create_client_pdu((FunctionCode) function, start_address, number_of_entities, payload, payload_len),
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
void send_pdu(std::span<const uint8_t> pdu) { this->parent_->send_pdu(this->address_, pdu, this); }
|
void send_pdu(std::span<const uint8_t> pdu) { this->parent_->send_pdu(this->address_, pdu, this); }
|
||||||
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload, this); }
|
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload, this); }
|
||||||
@@ -240,7 +240,7 @@ using ModbusDevice ESPDEPRECATED("Use ModbusClientDevice instead. Removed in 202
|
|||||||
|
|
||||||
// Transaction status: std::nullopt on success, otherwise the Modbus exception code. Server handlers return it;
|
// Transaction status: std::nullopt on success, otherwise the Modbus exception code. Server handlers return it;
|
||||||
// (future) client response callbacks receive it. Named without a side prefix so both directions share it.
|
// (future) client response callbacks receive it. Named without a side prefix so both directions share it.
|
||||||
using ResponseStatus = std::optional<ModbusExceptionCode>;
|
using ResponseStatus = std::optional<ExceptionCode>;
|
||||||
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
|
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
|
||||||
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
|
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
|
||||||
// the capacity of this type.
|
// the capacity of this type.
|
||||||
@@ -259,7 +259,7 @@ class ModbusServerDevice {
|
|||||||
uint8_t get_address() const { return this->address_; }
|
uint8_t get_address() const { return this->address_; }
|
||||||
virtual ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
virtual ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||||
RegisterValues ®isters) {
|
RegisterValues ®isters) {
|
||||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
return ExceptionCode::ILLEGAL_FUNCTION;
|
||||||
};
|
};
|
||||||
virtual ResponseStatus on_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
|
virtual ResponseStatus on_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||||
RegisterValues ®isters) {
|
RegisterValues ®isters) {
|
||||||
@@ -270,7 +270,7 @@ class ModbusServerDevice {
|
|||||||
return this->on_read_registers(start_address, number_of_registers, registers);
|
return this->on_read_registers(start_address, number_of_registers, registers);
|
||||||
};
|
};
|
||||||
virtual ResponseStatus on_write_registers(uint16_t start_address, const RegisterValues ®isters) {
|
virtual ResponseStatus on_write_registers(uint16_t start_address, const RegisterValues ®isters) {
|
||||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
return ExceptionCode::ILLEGAL_FUNCTION;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_1_END = 72; // 0x48
|
|||||||
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_INIT = 100; // 0x64
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_INIT = 100; // 0x64
|
||||||
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_END = 110; // 0x6E
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_END = 110; // 0x6E
|
||||||
|
|
||||||
enum class ModbusFunctionCode : uint8_t {
|
enum class FunctionCode : uint8_t {
|
||||||
INVALID = 0x00, // 0x00 is not a valid function code (even for custom functions).
|
INVALID = 0x00, // 0x00 is not a valid function code (even for custom functions).
|
||||||
CUSTOM = 0x00, // The CUSTOM alias should be removed in future.
|
CUSTOM = 0x00, // The CUSTOM alias should be removed in future.
|
||||||
READ_COILS = 0x01,
|
READ_COILS = 0x01,
|
||||||
@@ -37,14 +37,20 @@ enum class ModbusFunctionCode : uint8_t {
|
|||||||
READ_FIFO_QUEUE = 0x18, // not implemented
|
READ_FIFO_QUEUE = 0x18, // not implemented
|
||||||
};
|
};
|
||||||
|
|
||||||
/*Allow direct comparison operators between ModbusFunctionCode and uint8_t*/
|
// Remove before 2027.2.0
|
||||||
inline bool operator==(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) == rhs; }
|
using ModbusFunctionCode ESPDEPRECATED("Use modbus::FunctionCode instead. Removed in 2027.2.0",
|
||||||
inline bool operator==(uint8_t lhs, ModbusFunctionCode rhs) { return lhs == static_cast<uint8_t>(rhs); }
|
"2026.8.0") = FunctionCode;
|
||||||
inline bool operator!=(ModbusFunctionCode lhs, uint8_t rhs) { return !(static_cast<uint8_t>(lhs) == rhs); }
|
|
||||||
inline bool operator!=(uint8_t lhs, ModbusFunctionCode rhs) { return !(lhs == static_cast<uint8_t>(rhs)); }
|
|
||||||
|
|
||||||
// 4.3 MODBUS Data model
|
/*Allow direct comparison operators between FunctionCode and uint8_t*/
|
||||||
enum class ModbusRegisterType : uint8_t {
|
inline bool operator==(FunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) == rhs; }
|
||||||
|
inline bool operator==(uint8_t lhs, FunctionCode rhs) { return lhs == static_cast<uint8_t>(rhs); }
|
||||||
|
inline bool operator!=(FunctionCode lhs, uint8_t rhs) { return !(static_cast<uint8_t>(lhs) == rhs); }
|
||||||
|
inline bool operator!=(uint8_t lhs, FunctionCode rhs) { return !(lhs == static_cast<uint8_t>(rhs)); }
|
||||||
|
|
||||||
|
// 4.3 MODBUS Data model. "Entity" is the spec's umbrella for the four primary tables; only the
|
||||||
|
// 16-bit tables are registers (coils and discrete inputs are bits), so the enum is not named
|
||||||
|
// RegisterType.
|
||||||
|
enum class EntityType : uint8_t {
|
||||||
CUSTOM = 0x00,
|
CUSTOM = 0x00,
|
||||||
COIL = 0x01,
|
COIL = 0x01,
|
||||||
DISCRETE_INPUT = 0x02,
|
DISCRETE_INPUT = 0x02,
|
||||||
@@ -52,15 +58,17 @@ enum class ModbusRegisterType : uint8_t {
|
|||||||
// Named INPUT_REGISTER (not INPUT) because Arduino cores define INPUT as a macro.
|
// Named INPUT_REGISTER (not INPUT) because Arduino cores define INPUT as a macro.
|
||||||
INPUT_REGISTER = 0x04,
|
INPUT_REGISTER = 0x04,
|
||||||
// Remove before 2027.2.0
|
// Remove before 2027.2.0
|
||||||
READ ESPDEPRECATED("Use ModbusRegisterType::INPUT_REGISTER instead. Removed in 2027.2.0", "2026.7.0") =
|
READ ESPDEPRECATED("Use EntityType::INPUT_REGISTER instead. Removed in 2027.2.0", "2026.7.0") = INPUT_REGISTER,
|
||||||
INPUT_REGISTER,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove before 2027.2.0
|
||||||
|
using ModbusRegisterType ESPDEPRECATED("Use modbus::EntityType instead. Removed in 2027.2.0", "2026.8.0") = EntityType;
|
||||||
|
|
||||||
// 7 MODBUS Exception Responses:
|
// 7 MODBUS Exception Responses:
|
||||||
const uint8_t FUNCTION_CODE_MASK = 0x7F;
|
const uint8_t FUNCTION_CODE_MASK = 0x7F;
|
||||||
const uint8_t FUNCTION_CODE_EXCEPTION_MASK = 0x80;
|
const uint8_t FUNCTION_CODE_EXCEPTION_MASK = 0x80;
|
||||||
|
|
||||||
enum class ModbusExceptionCode : uint8_t {
|
enum class ExceptionCode : uint8_t {
|
||||||
ILLEGAL_FUNCTION = 0x01,
|
ILLEGAL_FUNCTION = 0x01,
|
||||||
ILLEGAL_DATA_ADDRESS = 0x02,
|
ILLEGAL_DATA_ADDRESS = 0x02,
|
||||||
ILLEGAL_DATA_VALUE = 0x03,
|
ILLEGAL_DATA_VALUE = 0x03,
|
||||||
@@ -72,6 +80,10 @@ enum class ModbusExceptionCode : uint8_t {
|
|||||||
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B,
|
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove before 2027.2.0
|
||||||
|
using ModbusExceptionCode ESPDEPRECATED("Use modbus::ExceptionCode instead. Removed in 2027.2.0",
|
||||||
|
"2026.8.0") = ExceptionCode;
|
||||||
|
|
||||||
// 6.12 16 (0x10) Write Multiple registers:
|
// 6.12 16 (0x10) Write Multiple registers:
|
||||||
static constexpr uint16_t MAX_NUM_OF_REGISTERS_TO_WRITE = 123; // 0x7B
|
static constexpr uint16_t MAX_NUM_OF_REGISTERS_TO_WRITE = 123; // 0x7B
|
||||||
|
|
||||||
|
|||||||
@@ -13,29 +13,29 @@ uint16_t server_frame_length(const uint8_t *frame, size_t size) {
|
|||||||
if (is_function_code_exception(frame[1])) {
|
if (is_function_code_exception(frame[1])) {
|
||||||
return 5; // address(1) + function(1) + exception(1) + CRC(2)
|
return 5; // address(1) + function(1) + exception(1) + CRC(2)
|
||||||
}
|
}
|
||||||
switch (static_cast<ModbusFunctionCode>(frame[1])) {
|
switch (static_cast<FunctionCode>(frame[1])) {
|
||||||
case ModbusFunctionCode::READ_COILS:
|
case FunctionCode::READ_COILS:
|
||||||
case ModbusFunctionCode::READ_DISCRETE_INPUTS:
|
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
case FunctionCode::READ_HOLDING_REGISTERS:
|
||||||
case ModbusFunctionCode::READ_INPUT_REGISTERS:
|
case FunctionCode::READ_INPUT_REGISTERS:
|
||||||
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
||||||
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_NUM_OF_REGISTERS_TO_READ * 2)) : 0);
|
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_NUM_OF_REGISTERS_TO_READ * 2)) : 0);
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_COIL:
|
case FunctionCode::WRITE_SINGLE_COIL:
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_REGISTER:
|
case FunctionCode::WRITE_SINGLE_REGISTER:
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_COILS:
|
case FunctionCode::WRITE_MULTIPLE_COILS:
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS:
|
case FunctionCode::WRITE_MULTIPLE_REGISTERS:
|
||||||
return 8; // address(1) + function(1) + output/register address(2) + value(2) + CRC(2)
|
return 8; // address(1) + function(1) + output/register address(2) + value(2) + CRC(2)
|
||||||
// Unsupported function codes. Included here to prevent parser failures. Excluding Serial Line specific functions.
|
// Unsupported function codes. Included here to prevent parser failures. Excluding Serial Line specific functions.
|
||||||
case ModbusFunctionCode::READ_FILE_RECORD:
|
case FunctionCode::READ_FILE_RECORD:
|
||||||
case ModbusFunctionCode::WRITE_FILE_RECORD:
|
case FunctionCode::WRITE_FILE_RECORD:
|
||||||
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
||||||
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_FRAME_SIZE - 5)) : 0);
|
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_FRAME_SIZE - 5)) : 0);
|
||||||
case ModbusFunctionCode::MASK_WRITE_REGISTER:
|
case FunctionCode::MASK_WRITE_REGISTER:
|
||||||
return 10; // address(1) + function(1) + reference address(2) + AND mask(2) + OR mask(2) + CRC(2)
|
return 10; // address(1) + function(1) + reference address(2) + AND mask(2) + OR mask(2) + CRC(2)
|
||||||
case ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS:
|
case FunctionCode::READ_WRITE_MULTIPLE_REGISTERS:
|
||||||
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
||||||
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_NUM_OF_REGISTERS_TO_READ * 2)) : 0);
|
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_NUM_OF_REGISTERS_TO_READ * 2)) : 0);
|
||||||
case ModbusFunctionCode::READ_FIFO_QUEUE:
|
case FunctionCode::READ_FIFO_QUEUE:
|
||||||
// address(1) + function(1) + fifo address(2) CRC(2)
|
// address(1) + function(1) + fifo address(2) CRC(2)
|
||||||
return 6;
|
return 6;
|
||||||
default:
|
default:
|
||||||
@@ -46,31 +46,31 @@ uint16_t server_frame_length(const uint8_t *frame, size_t size) {
|
|||||||
uint16_t client_frame_length(const uint8_t *frame, size_t size) {
|
uint16_t client_frame_length(const uint8_t *frame, size_t size) {
|
||||||
if (size < 2)
|
if (size < 2)
|
||||||
return MIN_FRAME_SIZE;
|
return MIN_FRAME_SIZE;
|
||||||
switch (static_cast<ModbusFunctionCode>(frame[1])) {
|
switch (static_cast<FunctionCode>(frame[1])) {
|
||||||
case ModbusFunctionCode::READ_COILS:
|
case FunctionCode::READ_COILS:
|
||||||
case ModbusFunctionCode::READ_DISCRETE_INPUTS:
|
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
case FunctionCode::READ_HOLDING_REGISTERS:
|
||||||
case ModbusFunctionCode::READ_INPUT_REGISTERS:
|
case FunctionCode::READ_INPUT_REGISTERS:
|
||||||
// address(1) + function(1) + start address(2) + quantity(2) + CRC(2)
|
// address(1) + function(1) + start address(2) + quantity(2) + CRC(2)
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_COIL:
|
case FunctionCode::WRITE_SINGLE_COIL:
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_REGISTER:
|
case FunctionCode::WRITE_SINGLE_REGISTER:
|
||||||
return 8; // address(1) + function(1) + output/register address(2) + value(2) + CRC(2)
|
return 8; // address(1) + function(1) + output/register address(2) + value(2) + CRC(2)
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_COILS:
|
case FunctionCode::WRITE_MULTIPLE_COILS:
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS:
|
case FunctionCode::WRITE_MULTIPLE_REGISTERS:
|
||||||
// address(1) + function(1) + start address(2) + quantity(2) + byte count(1) + data + CRC(2)
|
// address(1) + function(1) + start address(2) + quantity(2) + byte count(1) + data + CRC(2)
|
||||||
return 9 + (size > 6 ? std::min(frame[6], uint8_t(MAX_NUM_OF_REGISTERS_TO_WRITE * 2)) : 0);
|
return 9 + (size > 6 ? std::min(frame[6], uint8_t(MAX_NUM_OF_REGISTERS_TO_WRITE * 2)) : 0);
|
||||||
// Unsupported function codes. Included here to prevent parser failures. Excluding Serial Line specific functions.
|
// Unsupported function codes. Included here to prevent parser failures. Excluding Serial Line specific functions.
|
||||||
case ModbusFunctionCode::READ_FILE_RECORD:
|
case FunctionCode::READ_FILE_RECORD:
|
||||||
case ModbusFunctionCode::WRITE_FILE_RECORD:
|
case FunctionCode::WRITE_FILE_RECORD:
|
||||||
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
// address(1) + function(1) + byte count(1) + data + CRC(2)
|
||||||
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_FRAME_SIZE - 5)) : 0);
|
return 5 + (size > 2 ? std::min(frame[2], uint8_t(MAX_FRAME_SIZE - 5)) : 0);
|
||||||
case ModbusFunctionCode::MASK_WRITE_REGISTER:
|
case FunctionCode::MASK_WRITE_REGISTER:
|
||||||
return 10; // address(1) + function(1) + reference address(2) + AND mask(2) + OR mask(2) + CRC(2)
|
return 10; // address(1) + function(1) + reference address(2) + AND mask(2) + OR mask(2) + CRC(2)
|
||||||
case ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS:
|
case FunctionCode::READ_WRITE_MULTIPLE_REGISTERS:
|
||||||
// address(1) + function(1) + read start address(2) + read quantity(2) + write start address(2) +
|
// address(1) + function(1) + read start address(2) + read quantity(2) + write start address(2) +
|
||||||
// write quantity(2) + byte count(1) + data + CRC(2)
|
// write quantity(2) + byte count(1) + data + CRC(2)
|
||||||
return 13 + (size > 10 ? std::min(frame[10], uint8_t(MAX_NUM_OF_REGISTERS_TO_WRITE * 2)) : 0);
|
return 13 + (size > 10 ? std::min(frame[10], uint8_t(MAX_NUM_OF_REGISTERS_TO_WRITE * 2)) : 0);
|
||||||
case ModbusFunctionCode::READ_FIFO_QUEUE:
|
case FunctionCode::READ_FIFO_QUEUE:
|
||||||
// address(1) + function(1) + fifo address(2) CRC(2)
|
// address(1) + function(1) + fifo address(2) CRC(2)
|
||||||
return 6;
|
return 6;
|
||||||
default:
|
default:
|
||||||
@@ -197,7 +197,7 @@ std::optional<int64_t> registers_to_number(const uint16_t *registers, size_t cou
|
|||||||
return payload_to_number(bytes, required_size, sensor_value_type, 0, 0xFFFFFFFF);
|
return payload_to_number(bytes, required_size, sensor_value_type, 0, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(ModbusFunctionCode function_code, uint16_t start_address,
|
StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(FunctionCode function_code, uint16_t start_address,
|
||||||
uint16_t number_of_entities, const uint8_t *values,
|
uint16_t number_of_entities, const uint8_t *values,
|
||||||
size_t values_len) {
|
size_t values_len) {
|
||||||
if (is_function_code_read(static_cast<uint8_t>(function_code))) {
|
if (is_function_code_read(static_cast<uint8_t>(function_code))) {
|
||||||
@@ -221,33 +221,33 @@ StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(ModbusFunctionCode functio
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (function_code) {
|
switch (function_code) {
|
||||||
case ModbusFunctionCode::READ_COILS:
|
case FunctionCode::READ_COILS:
|
||||||
if (number_of_entities > MAX_NUM_OF_COILS_TO_READ) {
|
if (number_of_entities > MAX_NUM_OF_COILS_TO_READ) {
|
||||||
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum coils to read %u for function code %02X",
|
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum coils to read %u for function code %02X",
|
||||||
number_of_entities, MAX_NUM_OF_COILS_TO_READ, static_cast<uint8_t>(function_code));
|
number_of_entities, MAX_NUM_OF_COILS_TO_READ, static_cast<uint8_t>(function_code));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ModbusFunctionCode::READ_DISCRETE_INPUTS:
|
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||||
if (number_of_entities > MAX_NUM_OF_DISCRETE_INPUTS_TO_READ) {
|
if (number_of_entities > MAX_NUM_OF_DISCRETE_INPUTS_TO_READ) {
|
||||||
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum discrete inputs to read %u for function code %02X",
|
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum discrete inputs to read %u for function code %02X",
|
||||||
number_of_entities, MAX_NUM_OF_DISCRETE_INPUTS_TO_READ, static_cast<uint8_t>(function_code));
|
number_of_entities, MAX_NUM_OF_DISCRETE_INPUTS_TO_READ, static_cast<uint8_t>(function_code));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
case FunctionCode::READ_HOLDING_REGISTERS:
|
||||||
case ModbusFunctionCode::READ_INPUT_REGISTERS:
|
case FunctionCode::READ_INPUT_REGISTERS:
|
||||||
if (number_of_entities > MAX_NUM_OF_REGISTERS_TO_READ) {
|
if (number_of_entities > MAX_NUM_OF_REGISTERS_TO_READ) {
|
||||||
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum registers to read %u for function code %02X",
|
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum registers to read %u for function code %02X",
|
||||||
number_of_entities, MAX_NUM_OF_REGISTERS_TO_READ, static_cast<uint8_t>(function_code));
|
number_of_entities, MAX_NUM_OF_REGISTERS_TO_READ, static_cast<uint8_t>(function_code));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_COIL:
|
case FunctionCode::WRITE_SINGLE_COIL:
|
||||||
case ModbusFunctionCode::WRITE_SINGLE_REGISTER:
|
case FunctionCode::WRITE_SINGLE_REGISTER:
|
||||||
break; // number_of_entities is ignored for single write, so no need to validate
|
break; // number_of_entities is ignored for single write, so no need to validate
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_COILS:
|
case FunctionCode::WRITE_MULTIPLE_COILS:
|
||||||
case ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS:
|
case FunctionCode::WRITE_MULTIPLE_REGISTERS:
|
||||||
if (number_of_entities > MAX_NUM_OF_REGISTERS_TO_WRITE) {
|
if (number_of_entities > MAX_NUM_OF_REGISTERS_TO_WRITE) {
|
||||||
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum registers to write %u for function code %02X",
|
ESP_LOGE(TAG, "number_of_entities %u exceeds maximum registers to write %u for function code %02X",
|
||||||
number_of_entities, MAX_NUM_OF_REGISTERS_TO_WRITE, static_cast<uint8_t>(function_code));
|
number_of_entities, MAX_NUM_OF_REGISTERS_TO_WRITE, static_cast<uint8_t>(function_code));
|
||||||
@@ -263,15 +263,14 @@ StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(ModbusFunctionCode functio
|
|||||||
pdu.push_back(static_cast<uint8_t>(function_code));
|
pdu.push_back(static_cast<uint8_t>(function_code));
|
||||||
pdu.push_back(start_address >> 8);
|
pdu.push_back(start_address >> 8);
|
||||||
pdu.push_back(start_address >> 0);
|
pdu.push_back(start_address >> 0);
|
||||||
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
|
if (function_code != FunctionCode::WRITE_SINGLE_COIL && function_code != FunctionCode::WRITE_SINGLE_REGISTER) {
|
||||||
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
|
||||||
pdu.push_back(number_of_entities >> 8);
|
pdu.push_back(number_of_entities >> 8);
|
||||||
pdu.push_back(number_of_entities >> 0);
|
pdu.push_back(number_of_entities >> 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_function_code_write(static_cast<uint8_t>(function_code))) {
|
if (is_function_code_write(static_cast<uint8_t>(function_code))) {
|
||||||
if (function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
if (function_code == FunctionCode::WRITE_MULTIPLE_COILS ||
|
||||||
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
function_code == FunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
// 6 bytes of overhead (fc + start_addr×2 + qty×2 + byte_count) leave MAX_PDU_SIZE-6 bytes for values
|
// 6 bytes of overhead (fc + start_addr×2 + qty×2 + byte_count) leave MAX_PDU_SIZE-6 bytes for values
|
||||||
static constexpr size_t MAX_WRITE_MULTIPLE_VALUES_LEN = MAX_PDU_SIZE - 6;
|
static constexpr size_t MAX_WRITE_MULTIPLE_VALUES_LEN = MAX_PDU_SIZE - 6;
|
||||||
if (values_len > MAX_WRITE_MULTIPLE_VALUES_LEN) {
|
if (values_len > MAX_WRITE_MULTIPLE_VALUES_LEN) {
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
namespace esphome::modbus::helpers {
|
namespace esphome::modbus::helpers {
|
||||||
|
|
||||||
inline bool is_function_code_read(uint8_t function_code) {
|
inline bool is_function_code_read(uint8_t function_code) {
|
||||||
ModbusFunctionCode masked_function_code = static_cast<ModbusFunctionCode>(function_code & FUNCTION_CODE_MASK);
|
FunctionCode masked_function_code = static_cast<FunctionCode>(function_code & FUNCTION_CODE_MASK);
|
||||||
return masked_function_code == ModbusFunctionCode::READ_COILS ||
|
return masked_function_code == FunctionCode::READ_COILS ||
|
||||||
masked_function_code == ModbusFunctionCode::READ_DISCRETE_INPUTS ||
|
masked_function_code == FunctionCode::READ_DISCRETE_INPUTS ||
|
||||||
masked_function_code == ModbusFunctionCode::READ_HOLDING_REGISTERS ||
|
masked_function_code == FunctionCode::READ_HOLDING_REGISTERS ||
|
||||||
masked_function_code == ModbusFunctionCode::READ_INPUT_REGISTERS;
|
masked_function_code == FunctionCode::READ_INPUT_REGISTERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_function_code_write(uint8_t function_code) {
|
inline bool is_function_code_write(uint8_t function_code) {
|
||||||
ModbusFunctionCode masked_function_code = static_cast<ModbusFunctionCode>(function_code & FUNCTION_CODE_MASK);
|
FunctionCode masked_function_code = static_cast<FunctionCode>(function_code & FUNCTION_CODE_MASK);
|
||||||
return masked_function_code == ModbusFunctionCode::WRITE_SINGLE_COIL ||
|
return masked_function_code == FunctionCode::WRITE_SINGLE_COIL ||
|
||||||
masked_function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER ||
|
masked_function_code == FunctionCode::WRITE_SINGLE_REGISTER ||
|
||||||
masked_function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
masked_function_code == FunctionCode::WRITE_MULTIPLE_COILS ||
|
||||||
masked_function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS;
|
masked_function_code == FunctionCode::WRITE_MULTIPLE_REGISTERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_function_code_exception(uint8_t function_code) {
|
inline bool is_function_code_exception(uint8_t function_code) {
|
||||||
@@ -52,11 +52,11 @@ ESPDEPRECATED("Use server_pdu_payload() on the response PDU instead. Removed in
|
|||||||
inline uint8_t server_frame_data_offset(const uint8_t *frame, size_t size) {
|
inline uint8_t server_frame_data_offset(const uint8_t *frame, size_t size) {
|
||||||
if (size < 2)
|
if (size < 2)
|
||||||
return 0;
|
return 0;
|
||||||
switch (static_cast<ModbusFunctionCode>(frame[1])) {
|
switch (static_cast<FunctionCode>(frame[1])) {
|
||||||
case ModbusFunctionCode::READ_COILS:
|
case FunctionCode::READ_COILS:
|
||||||
case ModbusFunctionCode::READ_DISCRETE_INPUTS:
|
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
case FunctionCode::READ_HOLDING_REGISTERS:
|
||||||
case ModbusFunctionCode::READ_INPUT_REGISTERS:
|
case FunctionCode::READ_INPUT_REGISTERS:
|
||||||
return 3; // address(1) + function(1) + byte count(1) + data + CRC(2)
|
return 3; // address(1) + function(1) + byte count(1) + data + CRC(2)
|
||||||
default:
|
default:
|
||||||
return 2;
|
return 2;
|
||||||
@@ -100,32 +100,32 @@ inline bool value_type_is_float(SensorValueType v) {
|
|||||||
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
|
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
|
inline FunctionCode modbus_register_read_function(EntityType reg_type) {
|
||||||
switch (reg_type) {
|
switch (reg_type) {
|
||||||
case ModbusRegisterType::COIL:
|
case EntityType::COIL:
|
||||||
return ModbusFunctionCode::READ_COILS;
|
return FunctionCode::READ_COILS;
|
||||||
case ModbusRegisterType::DISCRETE_INPUT:
|
case EntityType::DISCRETE_INPUT:
|
||||||
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
|
return FunctionCode::READ_DISCRETE_INPUTS;
|
||||||
case ModbusRegisterType::HOLDING:
|
case EntityType::HOLDING:
|
||||||
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
|
return FunctionCode::READ_HOLDING_REGISTERS;
|
||||||
case ModbusRegisterType::INPUT_REGISTER:
|
case EntityType::INPUT_REGISTER:
|
||||||
return ModbusFunctionCode::READ_INPUT_REGISTERS;
|
return FunctionCode::READ_INPUT_REGISTERS;
|
||||||
default:
|
default:
|
||||||
return ModbusFunctionCode::INVALID;
|
return FunctionCode::INVALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type, bool multiple = false) {
|
inline FunctionCode modbus_register_write_function(EntityType reg_type, bool multiple = false) {
|
||||||
switch (reg_type) {
|
switch (reg_type) {
|
||||||
case ModbusRegisterType::COIL:
|
case EntityType::COIL:
|
||||||
return multiple ? ModbusFunctionCode::WRITE_MULTIPLE_COILS : ModbusFunctionCode::WRITE_SINGLE_COIL;
|
return multiple ? FunctionCode::WRITE_MULTIPLE_COILS : FunctionCode::WRITE_SINGLE_COIL;
|
||||||
case ModbusRegisterType::HOLDING:
|
case EntityType::HOLDING:
|
||||||
return multiple ? ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS : ModbusFunctionCode::WRITE_SINGLE_REGISTER;
|
return multiple ? FunctionCode::WRITE_MULTIPLE_REGISTERS : FunctionCode::WRITE_SINGLE_REGISTER;
|
||||||
// These register types can't be written (per spec)
|
// These register types can't be written (per spec)
|
||||||
case ModbusRegisterType::INPUT_REGISTER:
|
case EntityType::INPUT_REGISTER:
|
||||||
case ModbusRegisterType::DISCRETE_INPUT:
|
case EntityType::DISCRETE_INPUT:
|
||||||
default:
|
default:
|
||||||
return ModbusFunctionCode::INVALID;
|
return FunctionCode::INVALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +340,7 @@ std::optional<int64_t> registers_to_number(const uint16_t *registers, size_t cou
|
|||||||
* @param values_len length of values array
|
* @param values_len length of values array
|
||||||
* @return PDU (function code + data, no address, no CRC)
|
* @return PDU (function code + data, no address, no CRC)
|
||||||
*/
|
*/
|
||||||
StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(ModbusFunctionCode function_code, uint16_t start_address,
|
StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(FunctionCode function_code, uint16_t start_address,
|
||||||
uint16_t number_of_entities, const uint8_t *values = nullptr,
|
uint16_t number_of_entities, const uint8_t *values = nullptr,
|
||||||
size_t values_len = 0);
|
size_t values_len = 0);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from esphome.components import modbus
|
|||||||
from esphome.components.modbus.helpers import (
|
from esphome.components.modbus.helpers import (
|
||||||
MODBUS_REGISTER_TYPE,
|
MODBUS_REGISTER_TYPE,
|
||||||
TYPE_REGISTER_MAP,
|
TYPE_REGISTER_MAP,
|
||||||
ModbusRegisterType,
|
EntityType,
|
||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
|
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
|
||||||
@@ -217,13 +217,13 @@ async def register_modbus_device(var, config):
|
|||||||
|
|
||||||
def function_code_to_register(function_code):
|
def function_code_to_register(function_code):
|
||||||
FUNCTION_CODE_TYPE_MAP = {
|
FUNCTION_CODE_TYPE_MAP = {
|
||||||
"read_coils": ModbusRegisterType.COIL,
|
"read_coils": EntityType.COIL,
|
||||||
"read_discrete_inputs": ModbusRegisterType.DISCRETE_INPUT,
|
"read_discrete_inputs": EntityType.DISCRETE_INPUT,
|
||||||
"read_holding_registers": ModbusRegisterType.HOLDING,
|
"read_holding_registers": EntityType.HOLDING,
|
||||||
"read_input_registers": ModbusRegisterType.INPUT_REGISTER,
|
"read_input_registers": EntityType.INPUT_REGISTER,
|
||||||
"write_single_coil": ModbusRegisterType.COIL,
|
"write_single_coil": EntityType.COIL,
|
||||||
"write_single_register": ModbusRegisterType.HOLDING,
|
"write_single_register": EntityType.HOLDING,
|
||||||
"write_multiple_coils": ModbusRegisterType.COIL,
|
"write_multiple_coils": EntityType.COIL,
|
||||||
"write_multiple_registers": ModbusRegisterType.HOLDING,
|
"write_multiple_registers": EntityType.HOLDING,
|
||||||
}
|
}
|
||||||
return FUNCTION_CODE_TYPE_MAP[function_code]
|
return FUNCTION_CODE_TYPE_MAP[function_code]
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ void ModbusBinarySensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
|||||||
bool value;
|
bool value;
|
||||||
|
|
||||||
switch (this->register_type) {
|
switch (this->register_type) {
|
||||||
case ModbusRegisterType::DISCRETE_INPUT:
|
case EntityType::DISCRETE_INPUT:
|
||||||
case ModbusRegisterType::COIL:
|
case EntityType::COIL:
|
||||||
// offset for coil is the actual number of the coil not the byte offset
|
// offset for coil is the actual number of the coil not the byte offset
|
||||||
value = modbus::helpers::bit_from_packed(this->offset, data);
|
value = modbus::helpers::bit_from_packed(this->offset, data);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace esphome::modbus_controller {
|
|||||||
|
|
||||||
class ModbusBinarySensor final : public Component, public binary_sensor::BinarySensor, public SensorItem {
|
class ModbusBinarySensor final : public Component, public binary_sensor::BinarySensor, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
ModbusBinarySensor(EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||||
uint16_t skip_updates, bool force_new_range) {
|
uint16_t skip_updates, bool force_new_range) {
|
||||||
this->register_type = register_type;
|
this->register_type = register_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
@@ -20,7 +20,7 @@ class ModbusBinarySensor final : public Component, public binary_sensor::BinaryS
|
|||||||
this->skip_updates = skip_updates;
|
this->skip_updates = skip_updates;
|
||||||
this->force_new_range = force_new_range;
|
this->force_new_range = force_new_range;
|
||||||
|
|
||||||
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) {
|
if (register_type == EntityType::COIL || register_type == EntityType::DISCRETE_INPUT) {
|
||||||
this->register_count = offset + 1;
|
this->register_count = offset + 1;
|
||||||
} else {
|
} else {
|
||||||
this->register_count = 1;
|
this->register_count = 1;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ void ModbusController::process_modbus_data_(const ModbusCommandItem *response) {
|
|||||||
response->on_data_func(response->register_type, response->register_address, response->payload);
|
response->on_data_func(response->register_type, response->register_address, response->payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusController::on_error(std::span<const uint8_t> request_pdu, modbus::ModbusExceptionCode exception_code) {
|
void ModbusController::on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) {
|
||||||
// The request function code (request_pdu[0]) already carries what the log needs; the exception bit only
|
// The request function code (request_pdu[0]) already carries what the log needs; the exception bit only
|
||||||
// ever appears on the response, so no masking is needed here.
|
// ever appears on the response, so no masking is needed here.
|
||||||
const uint8_t function_code = request_pdu.empty() ? 0 : request_pdu[0];
|
const uint8_t function_code = request_pdu.empty() ? 0 : request_pdu[0];
|
||||||
@@ -116,7 +116,7 @@ void ModbusController::on_error(std::span<const uint8_t> request_pdu, modbus::Mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
|
SensorSet ModbusController::find_sensors_(EntityType register_type, uint16_t start_address) const {
|
||||||
auto reg_it = std::find_if(
|
auto reg_it = std::find_if(
|
||||||
std::begin(this->register_ranges_), std::end(this->register_ranges_),
|
std::begin(this->register_ranges_), std::end(this->register_ranges_),
|
||||||
[=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
|
[=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
|
||||||
@@ -130,7 +130,7 @@ SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint
|
|||||||
// not found
|
// not found
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
|
void ModbusController::on_register_data(EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
|
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
|
||||||
|
|
||||||
@@ -164,18 +164,18 @@ void ModbusController::update_range_(RegisterRange &r) {
|
|||||||
r.skip_updates_counter);
|
r.skip_updates_counter);
|
||||||
if (r.skip_updates_counter == 0) {
|
if (r.skip_updates_counter == 0) {
|
||||||
// if a custom command is used the user supplied custom_data is only available in the SensorItem.
|
// if a custom command is used the user supplied custom_data is only available in the SensorItem.
|
||||||
if (r.register_type == ModbusRegisterType::CUSTOM) {
|
if (r.register_type == EntityType::CUSTOM) {
|
||||||
auto sensors = this->find_sensors_(r.register_type, r.start_address);
|
auto sensors = this->find_sensors_(r.register_type, r.start_address);
|
||||||
if (!sensors.empty()) {
|
if (!sensors.empty()) {
|
||||||
auto sensor = sensors.cbegin();
|
auto sensor = sensors.cbegin();
|
||||||
auto command_item = ModbusCommandItem::create_custom_command(
|
auto command_item = ModbusCommandItem::create_custom_command(
|
||||||
this, (*sensor)->custom_data,
|
this, (*sensor)->custom_data,
|
||||||
[this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
[this](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
|
this->on_register_data(EntityType::CUSTOM, start_address, data);
|
||||||
});
|
});
|
||||||
command_item.register_address = (*sensor)->start_address;
|
command_item.register_address = (*sensor)->start_address;
|
||||||
command_item.register_count = (*sensor)->register_count;
|
command_item.register_count = (*sensor)->register_count;
|
||||||
command_item.function_code = ModbusFunctionCode::CUSTOM;
|
command_item.function_code = FunctionCode::CUSTOM;
|
||||||
queue_command(command_item);
|
queue_command(command_item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -237,7 +237,7 @@ size_t ModbusController::create_register_ranges_() {
|
|||||||
// this is not the first register in range so it might be possible
|
// this is not the first register in range so it might be possible
|
||||||
// to reuse the last register or extend the current range
|
// to reuse the last register or extend the current range
|
||||||
if (!curr->force_new_range && r.register_type == curr->register_type &&
|
if (!curr->force_new_range && r.register_type == curr->register_type &&
|
||||||
curr->register_type != ModbusRegisterType::CUSTOM) {
|
curr->register_type != EntityType::CUSTOM) {
|
||||||
if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
|
if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
|
||||||
curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
|
curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
|
||||||
// this register can re-use the data from the previous register
|
// this register can re-use the data from the previous register
|
||||||
@@ -347,7 +347,7 @@ void ModbusController::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
|
void ModbusController::on_write_register_response(EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
ESP_LOGV(TAG, "Command ACK 0x%X %d ", modbus::helpers::get_data<uint16_t>(data, 0),
|
ESP_LOGV(TAG, "Command ACK 0x%X %d ", modbus::helpers::get_data<uint16_t>(data, 0),
|
||||||
modbus::helpers::get_data<int16_t>(data, 1));
|
modbus::helpers::get_data<int16_t>(data, 1));
|
||||||
@@ -362,9 +362,8 @@ void ModbusController::dump_sensors_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModbusCommandItem ModbusCommandItem::create_read_command(
|
ModbusCommandItem ModbusCommandItem::create_read_command(
|
||||||
ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
|
ModbusController *modbusdevice, EntityType register_type, uint16_t start_address, uint16_t register_count,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> &&handler) {
|
||||||
&&handler) {
|
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = register_type;
|
cmd.register_type = register_type;
|
||||||
@@ -375,16 +374,15 @@ ModbusCommandItem ModbusCommandItem::create_read_command(
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbusdevice,
|
ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbusdevice, EntityType register_type,
|
||||||
ModbusRegisterType register_type, uint16_t start_address,
|
uint16_t start_address, uint16_t register_count) {
|
||||||
uint16_t register_count) {
|
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = register_type;
|
cmd.register_type = register_type;
|
||||||
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
|
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
|
||||||
cmd.register_address = start_address;
|
cmd.register_address = start_address;
|
||||||
cmd.register_count = register_count;
|
cmd.register_count = register_count;
|
||||||
cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
|
cmd.on_data_func = [modbusdevice](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
modbusdevice->on_register_data(register_type, start_address, data);
|
modbusdevice->on_register_data(register_type, start_address, data);
|
||||||
};
|
};
|
||||||
@@ -396,11 +394,11 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl
|
|||||||
const std::vector<uint16_t> &values) {
|
const std::vector<uint16_t> &values) {
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = ModbusRegisterType::HOLDING;
|
cmd.register_type = EntityType::HOLDING;
|
||||||
cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS;
|
cmd.function_code = FunctionCode::WRITE_MULTIPLE_REGISTERS;
|
||||||
cmd.register_address = start_address;
|
cmd.register_address = start_address;
|
||||||
cmd.register_count = register_count;
|
cmd.register_count = register_count;
|
||||||
cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
|
cmd.on_data_func = [modbusdevice, cmd](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||||
};
|
};
|
||||||
@@ -416,11 +414,11 @@ ModbusCommandItem ModbusCommandItem::create_write_single_coil(ModbusController *
|
|||||||
bool value) {
|
bool value) {
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = ModbusRegisterType::COIL;
|
cmd.register_type = EntityType::COIL;
|
||||||
cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_COIL;
|
cmd.function_code = FunctionCode::WRITE_SINGLE_COIL;
|
||||||
cmd.register_address = address;
|
cmd.register_address = address;
|
||||||
cmd.register_count = 1;
|
cmd.register_count = 1;
|
||||||
cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
|
cmd.on_data_func = [modbusdevice, cmd](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||||
};
|
};
|
||||||
@@ -433,11 +431,11 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle
|
|||||||
const std::vector<bool> &values) {
|
const std::vector<bool> &values) {
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = ModbusRegisterType::COIL;
|
cmd.register_type = EntityType::COIL;
|
||||||
cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_COILS;
|
cmd.function_code = FunctionCode::WRITE_MULTIPLE_COILS;
|
||||||
cmd.register_address = start_address;
|
cmd.register_address = start_address;
|
||||||
cmd.register_count = values.size();
|
cmd.register_count = values.size();
|
||||||
cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
|
cmd.on_data_func = [modbusdevice, cmd](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||||
};
|
};
|
||||||
@@ -465,11 +463,11 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle
|
|||||||
uint16_t value) {
|
uint16_t value) {
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.register_type = ModbusRegisterType::HOLDING;
|
cmd.register_type = EntityType::HOLDING;
|
||||||
cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_REGISTER;
|
cmd.function_code = FunctionCode::WRITE_SINGLE_REGISTER;
|
||||||
cmd.register_address = start_address;
|
cmd.register_address = start_address;
|
||||||
cmd.register_count = 1; // not used here anyways
|
cmd.register_count = 1; // not used here anyways
|
||||||
cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
|
cmd.on_data_func = [modbusdevice, cmd](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||||
};
|
};
|
||||||
@@ -482,13 +480,12 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle
|
|||||||
|
|
||||||
ModbusCommandItem ModbusCommandItem::create_custom_command(
|
ModbusCommandItem ModbusCommandItem::create_custom_command(
|
||||||
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
|
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> &&handler) {
|
||||||
&&handler) {
|
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.function_code = ModbusFunctionCode::CUSTOM;
|
cmd.function_code = FunctionCode::CUSTOM;
|
||||||
if (handler == nullptr) {
|
if (handler == nullptr) {
|
||||||
cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
cmd.on_data_func = [](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
ESP_LOGI(TAG, "Custom Command sent");
|
ESP_LOGI(TAG, "Custom Command sent");
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -501,13 +498,12 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
|
|||||||
|
|
||||||
ModbusCommandItem ModbusCommandItem::create_custom_command(
|
ModbusCommandItem ModbusCommandItem::create_custom_command(
|
||||||
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> &&handler) {
|
||||||
&&handler) {
|
|
||||||
ModbusCommandItem cmd = {};
|
ModbusCommandItem cmd = {};
|
||||||
cmd.modbusdevice = modbusdevice;
|
cmd.modbusdevice = modbusdevice;
|
||||||
cmd.function_code = ModbusFunctionCode::CUSTOM;
|
cmd.function_code = FunctionCode::CUSTOM;
|
||||||
if (handler == nullptr) {
|
if (handler == nullptr) {
|
||||||
cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
cmd.on_data_func = [](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
ESP_LOGI(TAG, "Custom Command sent");
|
ESP_LOGI(TAG, "Custom Command sent");
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -522,7 +518,7 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ModbusCommandItem::send() {
|
bool ModbusCommandItem::send() {
|
||||||
if (this->function_code != ModbusFunctionCode::CUSTOM) {
|
if (this->function_code != FunctionCode::CUSTOM) {
|
||||||
modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
|
modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
|
||||||
this->payload.empty() ? nullptr : &this->payload[0]);
|
this->payload.empty() ? nullptr : &this->payload[0]);
|
||||||
} else {
|
} else {
|
||||||
@@ -537,7 +533,7 @@ bool ModbusCommandItem::send() {
|
|||||||
bool ModbusCommandItem::is_equal(const ModbusCommandItem &other) {
|
bool ModbusCommandItem::is_equal(const ModbusCommandItem &other) {
|
||||||
// for custom commands we have to check for identical payloads, since
|
// for custom commands we have to check for identical payloads, since
|
||||||
// address/count/type fields will be set to zero
|
// address/count/type fields will be set to zero
|
||||||
return this->function_code == ModbusFunctionCode::CUSTOM
|
return this->function_code == FunctionCode::CUSTOM
|
||||||
? this->payload == other.payload
|
? this->payload == other.payload
|
||||||
: other.register_address == this->register_address && other.register_count == this->register_count &&
|
: other.register_address == this->register_address && other.register_count == this->register_count &&
|
||||||
other.register_type == this->register_type && other.function_code == this->function_code;
|
other.register_type == this->register_type && other.function_code == this->function_code;
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ namespace esphome::modbus_controller {
|
|||||||
|
|
||||||
class ModbusController;
|
class ModbusController;
|
||||||
|
|
||||||
|
using modbus::EntityType;
|
||||||
|
using modbus::ExceptionCode;
|
||||||
|
using modbus::FunctionCode;
|
||||||
|
using modbus::helpers::SensorValueType;
|
||||||
|
|
||||||
|
// Remove before 2027.2.0 - deprecated names re-exported so external components keep their warning window
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
using modbus::ModbusExceptionCode;
|
||||||
using modbus::ModbusFunctionCode;
|
using modbus::ModbusFunctionCode;
|
||||||
using modbus::ModbusRegisterType;
|
using modbus::ModbusRegisterType;
|
||||||
using modbus::ModbusExceptionCode;
|
#pragma GCC diagnostic pop
|
||||||
using modbus::helpers::SensorValueType;
|
|
||||||
|
|
||||||
// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
|
// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
|
||||||
ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
|
ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
|
||||||
inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
|
inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
|
||||||
|
|
||||||
ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
|
ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
|
||||||
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
|
inline FunctionCode modbus_register_read_function(EntityType reg_type) {
|
||||||
return modbus::helpers::modbus_register_read_function(reg_type);
|
return modbus::helpers::modbus_register_read_function(reg_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
|
ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
|
||||||
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
|
inline FunctionCode modbus_register_write_function(EntityType reg_type) {
|
||||||
return modbus::helpers::modbus_register_write_function(reg_type);
|
return modbus::helpers::modbus_register_write_function(reg_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +110,7 @@ class SensorItem {
|
|||||||
|
|
||||||
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
|
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
|
||||||
size_t virtual get_register_size() const {
|
size_t virtual get_register_size() const {
|
||||||
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) {
|
if (register_type == EntityType::COIL || register_type == EntityType::DISCRETE_INPUT) {
|
||||||
return 1;
|
return 1;
|
||||||
} else { // if CONF_RESPONSE_BYTES is used override the default
|
} else { // if CONF_RESPONSE_BYTES is used override the default
|
||||||
return response_bytes > 0 ? response_bytes : register_count * 2;
|
return response_bytes > 0 ? response_bytes : register_count * 2;
|
||||||
@@ -110,7 +118,7 @@ class SensorItem {
|
|||||||
}
|
}
|
||||||
// Override register size for modbus devices not using 1 register for one dword
|
// Override register size for modbus devices not using 1 register for one dword
|
||||||
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
|
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
|
||||||
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
|
EntityType register_type{EntityType::CUSTOM};
|
||||||
SensorValueType sensor_value_type{SensorValueType::RAW};
|
SensorValueType sensor_value_type{SensorValueType::RAW};
|
||||||
uint16_t start_address{0};
|
uint16_t start_address{0};
|
||||||
uint32_t bitmask{0};
|
uint32_t bitmask{0};
|
||||||
@@ -157,7 +165,7 @@ using SensorSet = std::set<SensorItem *, SensorItemsComparator>;
|
|||||||
|
|
||||||
struct RegisterRange {
|
struct RegisterRange {
|
||||||
uint16_t start_address;
|
uint16_t start_address;
|
||||||
ModbusRegisterType register_type;
|
EntityType register_type;
|
||||||
uint8_t register_count;
|
uint8_t register_count;
|
||||||
uint16_t skip_updates; // the config value
|
uint16_t skip_updates; // the config value
|
||||||
SensorSet sensors; // all sensors of this range
|
SensorSet sensors; // all sensors of this range
|
||||||
@@ -170,10 +178,9 @@ class ModbusCommandItem {
|
|||||||
ModbusController *modbusdevice{nullptr};
|
ModbusController *modbusdevice{nullptr};
|
||||||
uint16_t register_address{0};
|
uint16_t register_address{0};
|
||||||
uint16_t register_count{0};
|
uint16_t register_count{0};
|
||||||
ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
|
FunctionCode function_code{FunctionCode::CUSTOM};
|
||||||
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
|
EntityType register_type{EntityType::CUSTOM};
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> on_data_func;
|
||||||
on_data_func;
|
|
||||||
std::vector<uint8_t> payload = {};
|
std::vector<uint8_t> payload = {};
|
||||||
bool send();
|
bool send();
|
||||||
/// Check if the command should be retried based on the max_retries parameter
|
/// Check if the command should be retried based on the max_retries parameter
|
||||||
@@ -189,10 +196,10 @@ class ModbusCommandItem {
|
|||||||
* @param handler function called when the response is received
|
* @param handler function called when the response is received
|
||||||
* @return ModbusCommandItem with the prepared command
|
* @return ModbusCommandItem with the prepared command
|
||||||
*/
|
*/
|
||||||
static ModbusCommandItem create_read_command(
|
static ModbusCommandItem create_read_command(ModbusController *modbusdevice, EntityType register_type,
|
||||||
ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
|
uint16_t start_address, uint16_t register_count,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address,
|
||||||
&&handler);
|
const std::vector<uint8_t> &data)> &&handler);
|
||||||
/** Create modbus read command
|
/** Create modbus read command
|
||||||
* Function code 02-04
|
* Function code 02-04
|
||||||
* @param modbusdevice pointer to the device to execute the command
|
* @param modbusdevice pointer to the device to execute the command
|
||||||
@@ -201,7 +208,7 @@ class ModbusCommandItem {
|
|||||||
* @param register_count number of registers to read
|
* @param register_count number of registers to read
|
||||||
* @return ModbusCommandItem with the prepared command
|
* @return ModbusCommandItem with the prepared command
|
||||||
*/
|
*/
|
||||||
static ModbusCommandItem create_read_command(ModbusController *modbusdevice, ModbusRegisterType register_type,
|
static ModbusCommandItem create_read_command(ModbusController *modbusdevice, EntityType register_type,
|
||||||
uint16_t start_address, uint16_t register_count);
|
uint16_t start_address, uint16_t register_count);
|
||||||
/** Create modbus read command
|
/** Create modbus read command
|
||||||
* Function code 02-04
|
* Function code 02-04
|
||||||
@@ -251,7 +258,7 @@ class ModbusCommandItem {
|
|||||||
*/
|
*/
|
||||||
static ModbusCommandItem create_custom_command(
|
static ModbusCommandItem create_custom_command(
|
||||||
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
|
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
||||||
&&handler = nullptr);
|
&&handler = nullptr);
|
||||||
|
|
||||||
/** Create custom modbus command
|
/** Create custom modbus command
|
||||||
@@ -263,7 +270,7 @@ class ModbusCommandItem {
|
|||||||
*/
|
*/
|
||||||
static ModbusCommandItem create_custom_command(
|
static ModbusCommandItem create_custom_command(
|
||||||
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
||||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
std::function<void(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
||||||
&&handler = nullptr);
|
&&handler = nullptr);
|
||||||
|
|
||||||
bool is_equal(const ModbusCommandItem &other);
|
bool is_equal(const ModbusCommandItem &other);
|
||||||
@@ -296,13 +303,12 @@ class ModbusController final : public PollingComponent, public modbus::ModbusCli
|
|||||||
/// called when a modbus response was parsed without errors
|
/// called when a modbus response was parsed without errors
|
||||||
void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
|
void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
|
||||||
/// called when a modbus error response was received
|
/// called when a modbus error response was received
|
||||||
void on_error(std::span<const uint8_t> request_pdu, modbus::ModbusExceptionCode exception_code) override;
|
void on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override;
|
||||||
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
||||||
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
void on_register_data(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||||
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
||||||
/// incoming queue
|
/// incoming queue
|
||||||
void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
|
void on_write_register_response(EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||||
const std::vector<uint8_t> &data);
|
|
||||||
/// Allow a duplicate command to be sent
|
/// Allow a duplicate command to be sent
|
||||||
void set_allow_duplicate_commands(bool allow_duplicate_commands) {
|
void set_allow_duplicate_commands(bool allow_duplicate_commands) {
|
||||||
this->allow_duplicate_commands_ = allow_duplicate_commands;
|
this->allow_duplicate_commands_ = allow_duplicate_commands;
|
||||||
@@ -338,7 +344,7 @@ class ModbusController final : public PollingComponent, public modbus::ModbusCli
|
|||||||
/// parse sensormap_ and create range of sequential addresses
|
/// parse sensormap_ and create range of sequential addresses
|
||||||
size_t create_register_ranges_();
|
size_t create_register_ranges_();
|
||||||
// find register in sensormap. Returns iterator with all registers having the same start address
|
// find register in sensormap. Returns iterator with all registers having the same start address
|
||||||
SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const;
|
SensorSet find_sensors_(EntityType register_type, uint16_t start_address) const;
|
||||||
/// submit the read command for the address range to the send queue
|
/// submit the read command for the address range to the send queue
|
||||||
void update_range_(RegisterRange &r);
|
void update_range_(RegisterRange &r);
|
||||||
/// parse incoming modbus data
|
/// parse incoming modbus data
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ void ModbusNumber::control(float value) {
|
|||||||
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
||||||
write_cmd = ModbusCommandItem::create_custom_command(
|
write_cmd = ModbusCommandItem::create_custom_command(
|
||||||
this->parent_, data,
|
this->parent_, data,
|
||||||
[this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
[this, write_cmd](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data);
|
this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -77,7 +77,7 @@ void ModbusNumber::control(float value) {
|
|||||||
this->parent_, this->start_address + this->offset / 2, this->register_count, data);
|
this->parent_, this->start_address + this->offset / 2, this->register_count, data);
|
||||||
}
|
}
|
||||||
// publish new value
|
// publish new value
|
||||||
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
|
write_cmd.on_data_func = [this, write_cmd, value](EntityType register_type, uint16_t start_address,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
// gets called when the write command is ack'd from the device
|
// gets called when the write command is ack'd from the device
|
||||||
this->parent_->on_write_register_response(write_cmd.register_type, start_address, data);
|
this->parent_->on_write_register_response(write_cmd.register_type, start_address, data);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using value_to_data_t = std::function<float>(float);
|
|||||||
|
|
||||||
class ModbusNumber final : public number::Number, public Component, public SensorItem {
|
class ModbusNumber final : public number::Number, public Component, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
ModbusNumber(EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||||
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
||||||
this->register_type = register_type;
|
this->register_type = register_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ void ModbusBinaryOutput::write_state(bool state) {
|
|||||||
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
||||||
cmd = ModbusCommandItem::create_custom_command(
|
cmd = ModbusCommandItem::create_custom_command(
|
||||||
this->parent_, data,
|
this->parent_, data,
|
||||||
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
[this, cmd](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
|
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace esphome::modbus_controller {
|
|||||||
class ModbusFloatOutput final : public output::FloatOutput, public Component, public SensorItem {
|
class ModbusFloatOutput final : public output::FloatOutput, public Component, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) {
|
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) {
|
||||||
this->register_type = ModbusRegisterType::HOLDING;
|
this->register_type = EntityType::HOLDING;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
this->offset = offset;
|
this->offset = offset;
|
||||||
this->bitmask = 0xFFFFFFFF;
|
this->bitmask = 0xFFFFFFFF;
|
||||||
@@ -44,7 +44,7 @@ class ModbusFloatOutput final : public output::FloatOutput, public Component, pu
|
|||||||
class ModbusBinaryOutput final : public output::BinaryOutput, public Component, public SensorItem {
|
class ModbusBinaryOutput final : public output::BinaryOutput, public Component, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusBinaryOutput(uint16_t start_address, uint8_t offset) {
|
ModbusBinaryOutput(uint16_t start_address, uint8_t offset) {
|
||||||
this->register_type = ModbusRegisterType::COIL;
|
this->register_type = EntityType::COIL;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
this->bitmask = 0xFFFFFFFF;
|
this->bitmask = 0xFFFFFFFF;
|
||||||
this->sensor_value_type = SensorValueType::BIT;
|
this->sensor_value_type = SensorValueType::BIT;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ModbusSelect final : public Component, public select::Select, public Senso
|
|||||||
public:
|
public:
|
||||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint16_t skip_updates,
|
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint16_t skip_updates,
|
||||||
bool force_new_range, std::vector<int64_t> mapping) {
|
bool force_new_range, std::vector<int64_t> mapping) {
|
||||||
this->register_type = ModbusRegisterType::HOLDING; // not configurable
|
this->register_type = EntityType::HOLDING; // not configurable
|
||||||
this->sensor_value_type = sensor_value_type;
|
this->sensor_value_type = sensor_value_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
this->offset = 0; // not configurable
|
this->offset = 0; // not configurable
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace esphome::modbus_controller {
|
|||||||
|
|
||||||
class ModbusSensor final : public Component, public sensor::Sensor, public SensorItem {
|
class ModbusSensor final : public Component, public sensor::Sensor, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
ModbusSensor(EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||||
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
||||||
this->register_type = register_type;
|
this->register_type = register_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ bool ModbusSwitch::assumed_state() { return this->assumed_state_; }
|
|||||||
void ModbusSwitch::parse_and_publish(const std::vector<uint8_t> &data) {
|
void ModbusSwitch::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||||
bool value = false;
|
bool value = false;
|
||||||
switch (this->register_type) {
|
switch (this->register_type) {
|
||||||
case ModbusRegisterType::DISCRETE_INPUT:
|
case EntityType::DISCRETE_INPUT:
|
||||||
case ModbusRegisterType::COIL:
|
case EntityType::COIL:
|
||||||
// offset for coil is the actual number of the coil not the byte offset
|
// offset for coil is the actual number of the coil not the byte offset
|
||||||
value = modbus::helpers::bit_from_packed(this->offset, data);
|
value = modbus::helpers::bit_from_packed(this->offset, data);
|
||||||
break;
|
break;
|
||||||
@@ -82,13 +82,13 @@ void ModbusSwitch::write_state(bool state) {
|
|||||||
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
||||||
cmd = ModbusCommandItem::create_custom_command(
|
cmd = ModbusCommandItem::create_custom_command(
|
||||||
this->parent_, data,
|
this->parent_, data,
|
||||||
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
[this, cmd](EntityType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
|
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
|
ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
|
||||||
ONOFF(state), (int) this->register_type, this->start_address, this->offset);
|
ONOFF(state), (int) this->register_type, this->start_address, this->offset);
|
||||||
if (this->register_type == ModbusRegisterType::COIL) {
|
if (this->register_type == EntityType::COIL) {
|
||||||
// offset for coil and discrete inputs is the coil/register number not bytes
|
// offset for coil and discrete inputs is the coil/register number not bytes
|
||||||
if (this->use_write_multiple_) {
|
if (this->use_write_multiple_) {
|
||||||
std::vector<bool> states{state};
|
std::vector<bool> states{state};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace esphome::modbus_controller {
|
|||||||
|
|
||||||
class ModbusSwitch final : public Component, public switch_::Switch, public SensorItem {
|
class ModbusSwitch final : public Component, public switch_::Switch, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
ModbusSwitch(EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||||
uint16_t skip_updates, bool force_new_range) {
|
uint16_t skip_updates, bool force_new_range) {
|
||||||
this->register_type = register_type;
|
this->register_type = register_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
@@ -19,7 +19,7 @@ class ModbusSwitch final : public Component, public switch_::Switch, public Sens
|
|||||||
this->sensor_value_type = SensorValueType::BIT;
|
this->sensor_value_type = SensorValueType::BIT;
|
||||||
this->skip_updates = skip_updates;
|
this->skip_updates = skip_updates;
|
||||||
this->register_count = 1;
|
this->register_count = 1;
|
||||||
if (register_type == ModbusRegisterType::HOLDING || register_type == ModbusRegisterType::COIL) {
|
if (register_type == EntityType::HOLDING || register_type == EntityType::COIL) {
|
||||||
this->start_address += offset;
|
this->start_address += offset;
|
||||||
this->offset = 0;
|
this->offset = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
|
|||||||
|
|
||||||
class ModbusTextSensor final : public Component, public text_sensor::TextSensor, public SensorItem {
|
class ModbusTextSensor final : public Component, public text_sensor::TextSensor, public SensorItem {
|
||||||
public:
|
public:
|
||||||
ModbusTextSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count,
|
ModbusTextSensor(EntityType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count,
|
||||||
uint16_t response_bytes, RawEncoding encode, uint16_t skip_updates, bool force_new_range) {
|
uint16_t response_bytes, RawEncoding encode, uint16_t skip_updates, bool force_new_range) {
|
||||||
this->register_type = register_type;
|
this->register_type = register_type;
|
||||||
this->start_address = start_address;
|
this->start_address = start_address;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::modbus_server {
|
namespace esphome::modbus_server {
|
||||||
using modbus::ModbusExceptionCode;
|
using modbus::ExceptionCode;
|
||||||
using modbus::helpers::registers_to_number;
|
using modbus::helpers::registers_to_number;
|
||||||
|
|
||||||
static const char *const TAG = "modbus_server";
|
static const char *const TAG = "modbus_server";
|
||||||
@@ -50,13 +50,13 @@ modbus::ResponseStatus ModbusServer::on_read_registers(uint16_t start_address, u
|
|||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "No register at 0x%04X and courtesy default not allowed. Sending exception response.",
|
ESP_LOGW(TAG, "No register at 0x%04X and courtesy default not allowed. Sending exception response.",
|
||||||
static_cast<uint16_t>(current_address));
|
static_cast<uint16_t>(current_address));
|
||||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
return ExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server_register->read_lambda) {
|
if (!server_register->read_lambda) {
|
||||||
// Registered but not readable (write-only); don't mask it with the courtesy default.
|
// 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);
|
ESP_LOGW(TAG, "Register at 0x%04X is not readable. Sending exception response.", server_register->address);
|
||||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
return ExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A multi-register value is normally atomic: the request must start at its first register and cover all of
|
// A multi-register value is normally atomic: the request must start at its first register and cover all of
|
||||||
@@ -72,7 +72,7 @@ modbus::ResponseStatus ModbusServer::on_read_registers(uint16_t start_address, u
|
|||||||
"Read clips the multi-register value at 0x%04X, which does not allow partial reads. "
|
"Read clips the multi-register value at 0x%04X, which does not allow partial reads. "
|
||||||
"Sending exception response.",
|
"Sending exception response.",
|
||||||
server_register->address);
|
server_register->address);
|
||||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
return ExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t value = server_register->read_lambda();
|
int64_t value = server_register->read_lambda();
|
||||||
@@ -89,7 +89,7 @@ modbus::ResponseStatus ModbusServer::on_read_registers(uint16_t start_address, u
|
|||||||
// The value encoded to fewer words than its register span (e.g. a RAW register); treat as a device fault.
|
// 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,
|
ESP_LOGE(TAG, "Register at 0x%04X did not encode to %u registers", server_register->address,
|
||||||
server_register->register_count);
|
server_register->register_count);
|
||||||
return ModbusExceptionCode::SERVICE_DEVICE_FAILURE;
|
return ExceptionCode::SERVICE_DEVICE_FAILURE;
|
||||||
}
|
}
|
||||||
for (uint16_t i = 0; i < take; i++) {
|
for (uint16_t i = 0; i < take; i++) {
|
||||||
registers.push_back(value_words[value_offset + i]);
|
registers.push_back(value_words[value_offset + i]);
|
||||||
@@ -132,7 +132,7 @@ modbus::ResponseStatus ModbusServer::on_write_registers(uint16_t start_address,
|
|||||||
// so we never apply a partial write before discovering a problem. The commit pass below re-runs
|
// so we never apply a partial write before discovering a problem. The commit pass below re-runs
|
||||||
// registers_to_number rather than caching the decoded values: using the same function for the check and
|
// registers_to_number rather than caching the decoded values: using the same function for the check and
|
||||||
// the write keeps a single source of truth for the decode bound, independent of how register_count was set.
|
// the write keeps a single source of truth for the decode bound, independent of how register_count was set.
|
||||||
ModbusExceptionCode precheck = ModbusExceptionCode::ILLEGAL_DATA_ADDRESS; // unmatched or unwritable register
|
ExceptionCode precheck = ExceptionCode::ILLEGAL_DATA_ADDRESS; // unmatched or unwritable register
|
||||||
if (!for_each_register([&precheck, ®isters](ServerRegister *server_register, uint16_t register_offset) -> bool {
|
if (!for_each_register([&precheck, ®isters](ServerRegister *server_register, uint16_t register_offset) -> bool {
|
||||||
if (server_register->write_lambda == nullptr) {
|
if (server_register->write_lambda == nullptr) {
|
||||||
return false; // unwritable -> ILLEGAL_DATA_ADDRESS
|
return false; // unwritable -> ILLEGAL_DATA_ADDRESS
|
||||||
@@ -140,7 +140,7 @@ modbus::ResponseStatus ModbusServer::on_write_registers(uint16_t start_address,
|
|||||||
if (!registers_to_number(registers.data() + register_offset, registers.size() - register_offset,
|
if (!registers_to_number(registers.data() + register_offset, registers.size() - register_offset,
|
||||||
server_register->value_type)
|
server_register->value_type)
|
||||||
.has_value()) {
|
.has_value()) {
|
||||||
precheck = ModbusExceptionCode::ILLEGAL_DATA_VALUE; // request doesn't supply the full value
|
precheck = ExceptionCode::ILLEGAL_DATA_VALUE; // request doesn't supply the full value
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -158,7 +158,7 @@ modbus::ResponseStatus ModbusServer::on_write_registers(uint16_t start_address,
|
|||||||
return server_register->write_lambda(number);
|
return server_register->write_lambda(number);
|
||||||
})) {
|
})) {
|
||||||
ESP_LOGW(TAG, "A register write callback failed mid-sequence; earlier writes were already applied.");
|
ESP_LOGW(TAG, "A register write callback failed mid-sequence; earlier writes were already applied.");
|
||||||
return ModbusExceptionCode::SERVICE_DEVICE_FAILURE;
|
return ExceptionCode::SERVICE_DEVICE_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success: the caller builds the write response (an echo of the request header).
|
// Success: the caller builds the write response (an echo of the request header).
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace esphome::modbus::helpers {
|
namespace esphome::modbus::helpers {
|
||||||
|
|
||||||
using FC = ModbusFunctionCode;
|
using FC = FunctionCode;
|
||||||
|
|
||||||
// --- server_frame_length ---------------------------------------------------
|
// --- server_frame_length ---------------------------------------------------
|
||||||
// Frame layout: address(1) + function(1) + ... + CRC(2). Fixtures borrowed from
|
// Frame layout: address(1) + function(1) + ... + CRC(2). Fixtures borrowed from
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ binary_sensor:
|
|||||||
modbus_controller_id: modbus_controller1
|
modbus_controller_id: modbus_controller1
|
||||||
id: modbus_binary_sensor2
|
id: modbus_binary_sensor2
|
||||||
name: Test Binary Sensor with Lambda
|
name: Test Binary Sensor with Lambda
|
||||||
register_type: read
|
register_type: input
|
||||||
address: 0x3201
|
address: 0x3201
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return x;
|
return x;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace esphome::modbus_server {
|
namespace esphome::modbus_server {
|
||||||
|
|
||||||
using modbus::ModbusExceptionCode;
|
using modbus::ExceptionCode;
|
||||||
using modbus::RegisterValues;
|
using modbus::RegisterValues;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -73,7 +73,7 @@ TEST(ModbusServerWrite, UnderSuppliedValueAppliesNothing) {
|
|||||||
auto status = server.on_write_registers(0x0000, make_registers({0x1111, 0x2222}));
|
auto status = server.on_write_registers(0x0000, make_registers({0x1111, 0x2222}));
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_VALUE);
|
||||||
EXPECT_FALSE(word_written); // the writable WORD must NOT have been applied
|
EXPECT_FALSE(word_written); // the writable WORD must NOT have been applied
|
||||||
EXPECT_FALSE(dword_written);
|
EXPECT_FALSE(dword_written);
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ TEST(ModbusServerWrite, UnwritableRegisterRejected) {
|
|||||||
auto status = server.on_write_registers(0x0000, make_registers({0x1234}));
|
auto status = server.on_write_registers(0x0000, make_registers({0x1234}));
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An address with no registered register yields ILLEGAL_DATA_ADDRESS.
|
// An address with no registered register yields ILLEGAL_DATA_ADDRESS.
|
||||||
@@ -96,7 +96,7 @@ TEST(ModbusServerWrite, UnmatchedAddressRejected) {
|
|||||||
auto status = server.on_write_registers(0x0005, make_registers({0x1234}));
|
auto status = server.on_write_registers(0x0005, make_registers({0x1234}));
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A write_lambda failing at runtime is the one non-atomic case: the earlier register is already
|
// A write_lambda failing at runtime is the one non-atomic case: the earlier register is already
|
||||||
@@ -117,7 +117,7 @@ TEST(ModbusServerWrite, CallbackFailureIsServiceDeviceFailure) {
|
|||||||
auto status = server.on_write_registers(0x0000, make_registers({0xAAAA, 0xBBBB}));
|
auto status = server.on_write_registers(0x0000, make_registers({0xAAAA, 0xBBBB}));
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
EXPECT_EQ(status.value(), ExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||||
EXPECT_TRUE(first_written); // pre-validation passed, so the first write applied before the failure
|
EXPECT_TRUE(first_written); // pre-validation passed, so the first write applied before the failure
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ TEST(ModbusServerRead, StartInsideValueRejected) {
|
|||||||
auto status = server.on_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
auto status = server.on_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
EXPECT_FALSE(read_called);
|
EXPECT_FALSE(read_called);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ TEST(ModbusServerRead, ClippedTailRejected) {
|
|||||||
auto status = server.on_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
auto status = server.on_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
EXPECT_FALSE(read_called);
|
EXPECT_FALSE(read_called);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ TEST(ModbusServerRead, WriteOnlyRegisterRejected) {
|
|||||||
auto status = server.on_read_registers(0x0000, 1, out);
|
auto status = server.on_read_registers(0x0000, 1, out);
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An unregistered address with courtesy enabled returns the default value for each cell.
|
// An unregistered address with courtesy enabled returns the default value for each cell.
|
||||||
@@ -227,7 +227,7 @@ TEST(ModbusServerRead, UnregisteredRejectedWithoutCourtesy) {
|
|||||||
auto status = server.on_read_registers(0x0005, 1, out);
|
auto status = server.on_read_registers(0x0005, 1, out);
|
||||||
ASSERT_TRUE(status.has_value());
|
ASSERT_TRUE(status.has_value());
|
||||||
if (status.has_value())
|
if (status.has_value())
|
||||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
EXPECT_EQ(status.value(), ExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- partial reads (opt-in) ----------------------------------------------------
|
// --- partial reads (opt-in) ----------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user