Modbus controller docs (#1388)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Martin 2021-09-26 22:27:26 +02:00 committed by GitHub
parent 32ae4bf8e2
commit 33ba298b13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2342 additions and 4 deletions

View File

@ -0,0 +1,69 @@
Modbus Binary Sensor
====================
.. seo::
:description: Instructions for setting up a modbus_controller device binary sensor.
:image: modbus.png
The ``modbus_controller`` binary sensor platform creates a binary sensor from a modbus_controller component
and requires :doc:`/components/modbus_controller` to be configured.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- **modbus_functioncode** (**Required**): type of the modbus register.
- "read_coils": Function 01 (01hex) Read Coils - Reads the ON/OFF status of discrete coils in the device.
- "read_discrete_inputs": Function 02(02hex) - Reads the ON/OFF status of discrete inputs in the device.
- "read_holding_registers": Function 03 (03hex) Read Holding Registers - Read the binary contents of holding registers in the device.
- "read_input_registers": Function 04 (04hex) Read Input Registers - Read the binary contents of input registers in the device.
- **address**: (**Required**, integer): start address of the first register in a range
- **bitmask** : some values are packed in a response. The bitmask is used to determined if the result is true or false
- **skip_updates**: (*Optional*, integer): By default all sensors of of a modbus_controller are updated together. For data points that don't change very frequently updates can be skipped. A value of 5 would only update this sensor range in every 5th update cycle
- **force_new_range**: (*Optional*, boolean): If possible sensors with sequential addresses are grouped together and requested in one range. Setting `foce_new_range: true` enforces the start of a new range at that address.
- **lambda** (*Optional*, :ref:`lambda <config-lambda>`):
Lambda to be evaluated every update interval to get the new value of the sensor
- **offset**: (*Optional*, integer): not required for most cases
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
The value for offset depends on the register type. If a binary_sensor is created from an input register the offset is in bytes. For coil and discrete input resisters the LSB of the first data byte contains the coil addressed in the request. The other coils follow toward the high-order end of this byte and from low order to high order in subsequent bytes. For the registers offset is the position of the relevant bit.
To get the value of the coil register 2 can be retrived using address: 2 / offset: 0 or address: 0 / offset 2
Example
.. code-block:: yaml
binary_sensor:
- platform: modbus_controller
modbus_controller_id: epever
id: battery_internal_resistance_abnormal
name: "Battery internal resistance abnormal"
register_type: read
address: 0x3200
bitmask: 0x80 #(bit 8)
Parameters passed into the lambda
- **x** (bool): The parsed float value of the modbus data
- **data** (std::vector<uint8_t): vector containing the complete raw modbus response bytes for this sensor
- **item** (const pointer to a ModbusBinarySensor object): The sensor object itself.
Possible return values for the lambda:
- ``return true/false;`` the new value for the sensor.
See Also
--------
- :apiclass:`:modbus_controller::ModbusBinarySensor`
- :doc:`/components/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- :doc:`/components/output/modbus_controller`
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- https://www.modbustools.com/modbus.html
- :ghedit:`Edit`

View File

@ -39,9 +39,9 @@ Configuration variables:
:ref:`float output <output>` connected to Pin A (alternatively IN1, etc.) of the h-bridge.
- **pin_b** (**Required**, :ref:`config-id`): The id of the
:ref:`float output <output>` connected to Pin B (alternatively IN2, etc.) of the h-bridge.
- **enable_pin** (**Optional**, :ref:`config-id`): The id of the
- **enable_pin** (*Optional*, :ref:`config-id`): The id of the
:ref:`float output <output>` connected to the Enable pin of the h-bridge (if h-bridge uses enable).
- **decay_mode** (**Optional**, string): The decay mode you want to use with
- **decay_mode** (*Optional*, string): The decay mode you want to use with
the h-bridge. Either ``slow`` (braking) or ``fast`` (coasting). Defaults to ``slow``.
- **name** (**Required**, string): The name for this fan.
- **oscillation_output** (*Optional*, :ref:`config-id`): The id of the

View File

@ -24,8 +24,14 @@ Configuration variables:
------------------------
- **flow_control_pin** (*Optional*, :ref:`config-pin`): The pin used to switch flow control.
This is useful for RS485 transeivers that do not have automatic flow control switching,
like the common MAX485.
This is useful for RS485 transeivers that do not have automatic flow control switching,
like the common MAX485.
- **send_wait_time** (*Optional*, :ref:`time`): Time in milliseconds before a new modbus command is sent if an answer from a previous command is pending. Defaults to 250 ms.
If multiple modbus devices are attached increasing this value can help avoiding to to overlapping reads.
When 2 devices are sending a command at the same the the response read from uart can't be assigend to the proper design.
This value defines the maximumm queuing time for a command before it is send anyways.
See Also
--------

View File

@ -0,0 +1,464 @@
Modbus Controller
=================
.. seo::
:description: Instructions for setting up the Modbus Controller component.
:image: modbus.png
The ``modbus_controller`` component creates a RS485 connection to control a modbus device
.. warning::
If you are using the :doc:`logger` uart logging might interfere especially on esp8266. You can disable the uart logging with the ``baud_rate: 0`` option.
.. figure:: /images/modbus.png
:align: center
:width: 25%
The ``modbus_controller`` component uses the modbus component
Hardware setup
--------------
I'm using a RS 485 module connected to an ESP32
.. figure:: /images/rs485.jpg
See [How is this RS485 Module Working?](https://electronics.stackexchange.com/questions/244425/how-is-this-rs485-module-working) on stackexchange for more details
The controller connects to the UART of the MCU. For ESP32 GPIO PIN 16 to TXD PIN 17 to RXD are the default ports but any other pins can be used as well . 3.3V to VCC and GND to GND.
Configuration variables:
------------------------
- **modbus_id** (*Optional*, :ref:`config-id`): Manually specify the ID of the modbus hub.
- **address** (*Required*, :ref:`config-id`): The modbus address of the device
Specify the modbus device address of the.
- **command_throttle** (*Optional*, int): minimum time in milliseconds between 2 requests to the device. Default is 0ms
Because some modbus devices limit the rate of requests the interval between sending requests to the device can be modified.
Getting started with Home Assistant
-----------------------------------
The following code create a modbus_controller hub talking to a modbus device at address 1 with 115200 bps
Modbus sensors can be directly defined (inline) under the modbus_controller hub or as standalone components
Technically there is no difference between the "inline" and the standard definitions approach.
.. code-block:: yaml
esphome:
name: solarstation
platform: ESP32
board: esp32dev
substitutions:
updates: 30s
wifi:
ssid: !secret wifi_sid
password: !secret wifi_password
reboot_timeout: 2min
logger:
level: INFO
baud_rate: 0
api:
password: !secret api_password
uart:
id: mod_bus
tx_pin: 17
rx_pin: 16
baud_rate: 115200
stop_bits: 1
modbus:
flow_control_pin: 5
id: modbus1
modbus_controller:
- id: epever
## the Modbus device addr
address: 0x1
modbus_id: modbus1
setup_priority: -10
text_sensor:
- name: "rtc_clock"
platform: modbus_controller
modbus_controller_id: epever
id: rtc_clock
internal: true
register_type: holding
address: 0x9013
register_count: 3
raw_encode: HEXBYTES
response_size: 6
switch:
- platform: modbus_controller
modbus_controller_id: epever
id: reset_to_fabric_default
name: "Reset to Factory Default"
register_type: coil
address: 0x15
bitmask: 1
sensor:
- platform: modbus_controller
modbus_controller_id: epever
name: "Battery Capacity"
id: battery_capacity
register_type: holding
address: 0x9001
unit_of_measurement: "AH"
value_type: U_WORD
Protocol decoding example
-------------------------
.. code-block:: yaml
sensors:
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_voltage
name: "array_rated_voltage"
address: 0x3000
unit_of_measurement: "V"
register_type: read
value_type: U_WORD
accuracy_decimals: 1
skip_updates: 60
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_current
name: "array_rated_current"
address: 0x3001
unit_of_measurement: "V"
register_type: read
value_type: U_WORD
accuracy_decimals: 2
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_power
name: "array_rated_power"
address: 0x3002
unit_of_measurement: "W"
register_type: read
value_type: U_DWORD_R
accuracy_decimals: 1
filters:
- multiply: 0.01
-platform: modbus_controller
modbus_controller_id: epever
id: battery_rated_voltage
name: "battery_rated_voltage"
address: 0x3004
unit_of_measurement: "V"
register_type: read
value_type: U_WORD
accuracy_decimals: 1
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: battery_rated_current
name: "battery_rated_current"
address: 0x3005
unit_of_measurement: "A"
register_type: read
value_type: U_WORD
accuracy_decimals: 1
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: battery_rated_power
name: "battery_rated_power"
address: 0x3006
unit_of_measurement: "W"
register_type: read
value_type: U_DWORD_R
accuracy_decimals: 1
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever id: charging_mode
name: "charging_mode"
address: 0x3008
unit_of_measurement: ""
register_type: read
value_type: U_WORD
accuracy_decimals: 0
To minimize the required transactions all registers with the same base address are read in one request.
The response is mapped to the sensor based on register_count and offset in bytes.
**Request**
+-----------+-----------------------------------------+
| data | description |
+===========+=========================================+
| 0x1 (01) | device address |
+-----------+-----------------------------------------+
| 0x4 (04) | function code 4 (Read Input Registers) |
+-----------+-----------------------------------------+
| 0x30 (48) | start address high byte |
+-----------+-----------------------------------------+
| 0x0 (00) | start address low byte |
+-----------+-----------------------------------------+
| 0x0 (00) | number of registers to read high byte |
+-----------+-----------------------------------------+
| 0x9 (09) | number of registers to read low byte |
+-----------+-----------------------------------------+
| 0x3f (63) | crc |
+-----------+-----------------------------------------+
| 0xc (12) | crc |
+-----------+-----------------------------------------+
**Response**
+--------+------------+--------------------+--------------------------------------------+
| offset | data | value (type) | description |
+========+============+====================+============================================+
| H | 0x1 (01) | | device address |
+--------+------------+--------------------+--------------------------------------------+
| H | 0x4 (04) | | function code |
+--------+------------+--------------------+--------------------------------------------+
| H | 0x12 (18) | | byte count |
+--------+------------+--------------------+--------------------------------------------+
| 0 | 0x27 (39) | U_WORD | array_rated_voltage high byte |
+--------+------------+--------------------+--------------------------------------------+
| 1 | 0x10 (16) | 0x2710 (100000) | array_rated_voltage low byte |
+--------+------------+--------------------+--------------------------------------------+
| 2 | 0x7 (7) | U_WORD | array_rated_current high byte |
+--------+------------+--------------------+--------------------------------------------+
| 3 | 0xd0 (208) | 0x7d0 (2000) | array_rated_current low byte |
+--------+------------+--------------------+--------------------------------------------+
| 4 | 0xcb (203) | U_DWORD_R | array_rated_power high byte of low word |
+--------+------------+--------------------+--------------------------------------------+
| 5 | 0x20 (32) | spans 2 register | array_rated_power low byte of low word |
+--------+------------+--------------------+--------------------------------------------+
| 6 | 0x0 (0) | | array_rated_power high byte of high word |
+--------+------------+--------------------+--------------------------------------------+
| 7 | 0x0 (0) | 0x0000CB20 (52000) | array_rated_power low byte of high word |
+--------+------------+--------------------+--------------------------------------------+
| 8 | 0x9 (09) | U_WORD | battery_rated_voltage high byte |
+--------+------------+--------------------+--------------------------------------------+
| 9 | 0x60 (96) | 0x960 (2400) | battery_rated_voltage low byte |
+--------+------------+--------------------+--------------------------------------------+
| 10 | 0x7 (07) | U_WORD | battery_rated_current high word |
+--------+------------+--------------------+--------------------------------------------+
| 11 | 0xd0 (208) | 0x7d0 (2000) | battery_rated_current high word |
+--------+------------+--------------------+--------------------------------------------+
| 12 | 0xcb (203) | U_DWORD_R | battery_rated_power high byte of low word |
+--------+------------+--------------------+--------------------------------------------+
| 13 | 0x20 (32) | spans 2 register | battery_rated_power low byte of low word |
+--------+------------+--------------------+--------------------------------------------+
| 14 | 0x0 (0) | | battery_rated_power high byte of high word |
+--------+------------+--------------------+--------------------------------------------+
| 15 | 0x0 (0) | 0x0000CB20 (52000) | battery_rated_power low byte of high word |
+--------+------------+--------------------+--------------------------------------------+
| 16 | 0x0 (0) | U_WORD | charging_mode high byte |
+--------+------------+--------------------+--------------------------------------------+
| 17 | 0x2 (02) | 0x2 (MPPT) | charging_mode low byte |
+--------+------------+--------------------+--------------------------------------------+
| C | 0x2f (47) | | crc |
+--------+------------+--------------------+--------------------------------------------+
| C | 0x31 (49) | | crc |
+--------+------------+--------------------+--------------------------------------------+
Note
----
Write support is only implemented for switches.
However the C++ code provides the required API to write to a modbus device.
These methods can be called from a lambda.
Here is an example how to set config values to for an EPEVER Trace AN controller.
The code synchronizes the localtime of MCU to the epever controller
The time is set by writing 12 bytes to register 0x9013.
Then battery charge settings are sent.
.. code-block:: yaml
esphome:
name: solarstation-test
platform: ESP32
board: esp32dev
## send config values at startup
## configure rtc clock and battery charge settings
on_boot:
priority: -100
then:
- lambda: |-
on_boot:
## configure controller settings at setup
## make sure priority is lower than setup_priority of modbus_controller
priority: -100
then:
- lambda: |-
// get local time and sync to controller
time_t now = ::time(nullptr);
struct tm *time_info = ::localtime(&now);
int seconds = time_info->tm_sec;
int minutes = time_info->tm_min;
int hour = time_info->tm_hour;
int day = time_info->tm_mday;
int month = time_info->tm_mon + 1;
int year = time_info->tm_year % 100;
esphome::modbus_controller::ModbusController *controller = id(epever);
// if there is no internet connection localtime returns year 70
if (year != 70) {
// create the payload
std::vector<uint16_t> rtc_data = {uint16_t((minutes << 8) | seconds), uint16_t((day << 8) | hour),
uint16_t((year << 8) | month)};
// Create a modbus command item with the time information as the payload
esphome::modbus_controller::ModbusCommandItem set_rtc_command =
esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 0x9013, 3, rtc_data);
// Submit the command to the send queue
epever->queue_command(set_rtc_command);
ESP_LOGI("ModbusLambda", "EPSOLAR RTC set to %02d:%02d:%02d %02d.%02d.%04d", hour, minutes, seconds, day, month,
year + 2000);
}
// Battery settings
// Note: these values are examples only and apply my AGM Battery
std::vector<uint16_t> battery_settings1 = {
0, // 9000 Battery Type 0 = User
0x0073, // 9001 Battery Cap 0x55 == 115AH
0x012C, // 9002 Temp compensation -3V /°C/2V
0x05DC, // 9003 0x5DC == 1500 Over Voltage Disconnect Voltage 15,0
0x058C, // 9004 0x58C == 1480 Charging Limit Voltage 14,8
0x058C, // 9005 Over Voltage Reconnect Voltage 14,8
0x05BF, // 9006 Equalize Charging Voltage 14,6
0x05BE, // 9007 Boost Charging Voltage 14,7
0x0550, // 9008 Float Charging Voltage 13,6
0x0528, // 9009 Boost Reconnect Charging Voltage 13,2
0x04C4, // 900A Low Voltage Reconnect Voltage 12,2
0x04B0, // 900B Under Voltage Warning Reconnect Voltage 12,0
0x04BA, // 900c Under Volt. Warning Volt 12,1
0x04BA, // 900d Low Volt. Disconnect Volt. 11.8
0x04BA // 900E Discharging Limit Voltage 11.8
};
// Boost and equalization periods
std::vector<uint16_t> battery_settings2 = {
0x0000, // 906B Equalize Duration (min.) 0
0x0075 // 906C Boost Duration (aka absorb) 117 mins
};
esphome::modbus_controller::ModbusCommandItem set_battery1_command =
esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 0x9000, battery_settings1.size() ,
battery_settings1);
esphome::modbus_controller::ModbusCommandItem set_battery2_command =
esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 0x906B, battery_settings3.size(),
battery_settings2);
delay(200) ;
controller->queue_command(set_battery1_command);
delay(200) ;
controller->queue_command(set_battery2_command);
ESP_LOGI("ModbusLambda", "EPSOLAR Battery set");
uart:
id: mod_bus
tx_pin: 19
rx_pin: 18
baud_rate: 115200
stop_bits: 1
modbus:
#flow_control_pin: 23
send_wait_time: 200ms
id: mod_bus_epever
modbus_controller:
- id: epever
## the Modbus device addr
address: 0x1
modbus_id: mod_bus_epever
command_throttle: 0ms
setup_priority: -10
update_interval: ${updates}
sensor:
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_voltage
name: "array_rated_voltage"
address: 0x3000
unit_of_measurement: "V"
register_type: read
value_type: U_WORD
accuracy_decimals: 1
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_current
name: "array_rated_current"
address: 0x3001
unit_of_measurement: "A"
register_type: read
value_type: U_WORD
accuracy_decimals: 2
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: epever
id: array_rated_power
name: "array_rated_power"
address: 0x3002
unit_of_measurement: "W"
register_type: read
value_type: U_DWORD_R
accuracy_decimals: 1
filters:
- multiply: 0.01
See Also
--------
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- :doc:`/components/number/modbus_controller`
- :doc:`/components/output/modbus_controller`
- :doc:`EPEVER MPPT Solar Charge Controller Tracer-AN Series</cookbook/tracer-an>`
- `Modbus RTU Protocol Description <https://www.modbustools.com/modbus.html>`__
- :ghedit:`Edit`

View File

@ -0,0 +1,112 @@
Modbus Controller Number
========================
.. seo::
:description: Instructions for setting up a modbus_controller device sensor.
:image: modbus_controller.png
The ``modbus_controller`` platform creates a Number from a modbus_controller.
When the Number is updated a modbus write command is created sent to the device.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- coil: coils are also called discrete outout. Coils are 1-bit registers (on/off values) that are used to control discrete outputs. Read and Write access
- discrete_input: discrete input register (read only coil) are similar to coils but can only be read.
- holding: Holding Registers - Holding registers are the most universal 16-bit register. Read and Write access
- read: Read Input Registers - registers are 16-bit registers used for input, and may only be read
- **address**: (**Required**, int): start address of the first register in a range
- **value_type**: (**Required**): datatype of the mod_bus register data. The default data type for modbus is a 16 bit integer in big endian format (MSB first)
- U_WORD (unsigned float from 1 register =16bit
- S_WORD (signed float from one register)
- U_DWORD (unsigned float from 2 registers = 32bit)
- S_DWORD (unsigned float from 2 registers = 32bit)
- U_DWORD_R (unsigend float from 2 registers low word first )
- S_DWORD_R (sigend float from 2 registers low word first )
- U_QWORD (unsigned float from 4 registers = 64bit
- S_QWORD (signed float from 4 registers = 64bit
- U_QWORD_R (unsigend float from 4 registers low word first )
- S_QWORD_R (sigend float from 4 registers low word first )
- **skip_updates**: (*Optional*, integer): By default all sensors of of a modbus_controller are updated together. For data points that don't change very frequently updates can be skipped. A value of 5 would only update this sensor range in every 5th update cycle
Note: The modbus_controller groups component by address ranges to reduce number of transactions. All compoents with the same address will be updated in one request. skip_updates applies for all components in the same range.
- **register_count**: (*Optional*): only required for uncommon response encodings
The number of registers this data point spans. Default is 1
- **force_new_range**: (*Optional*, boolean): If possible sensors with sequential addresses are grouped together and requested in one range. Setting `foce_new_range: true` enforces the start of a new range at that address.
- **offset**: (*Optional*, int): only required for uncommon response encodings
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
- **min_value** (*Optional*, float): The minimum value this number can be.
- **max_value** (*Optional*, float): The maximum value this number can be.
- **step** (*Optional*, float): The granularity with which the number can be set. Defaults to 1
- **lambda** (*Optional*, :ref:`lambda <config-lambda>`):
Lambda to be evaluated every update interval to get the new value of the sensor.
- **write_lambda** (*Optional*, :ref:`lambda <config-lambda>`): Lambda called before send.
Lambda is evaluated before the modbus write command is created.
- **multiply** (*Optional*, float): multiply the new value with this factor before sending the requests. Ignored if lambda is defined.
All other options from :ref:`Number <config-number>`.
Parameters passed into the lambda
- **x** (float): The parsed float value of the modbus data
- **data** (std::vector<uint8_t): vector containing the complete raw modbus response bytes for this sensor
note: because the response contains data for all registers in the same range you have to use `data[item->offset]` to get the first response byte for your sensor.
- **item** (const pointer to a SensorItem derived object): The sensor object itself.
Possible return values for the lambda:
- ``return <FLOATING_POINT_NUMBER>;`` the new value for the sensor.
- ``return NAN;`` if the state should be considered invalid to indicate an error (advanced).
**Parameters passed into write_lambda**
- **x** (float): The float value to be sent to the modbus device
- **payload** (`std::vector<uint16_t>&payload`): empty vector for the payload. The lamdba can add 16 bit raw modbus register words.
note: because the response contains data for all registers in the same range you have to use `data[item->offset]` to get the first response byte for your sensor.
- **item** (const pointer to a SensorItem derived object): The sensor object itself.
Possible return values for the lambda:
- ``return <FLOATING_POINT_NUMBER>;`` the new value for the sensor.
- ``return <anything>; and fill payload with data`` if the payload is added from the lambda then these 16 bit words will be sent
- ``return {};`` if you don't want write the command to the device (or do it from the lambda).
**Example**
.. code-block:: yaml
number:
- platform: modbus_controller
modbus_controller_id: epever
id: battery_capacity_number
name: "Battery Cap Number"
address: 0x9001
register_type: holding
value_type: U_WORD
lambda: "return x * 1.0; "
write_lambda: |-
ESP_LOGD("main","Modbus Number incoming value = %f",x);
uint16_t b_capacity = x ;
payload.push_back(b_capacity);
return x * 1.0 ;
## multiply is ignored because lamdba is used
multiply: 1.0
See Also
--------
- :doc:`/components/modbus_controller`
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- :doc:`/components/output/modbus_controller`
- https://www.modbustools.com/modbus.html
- :ghedit:`Edit`

View File

@ -0,0 +1,82 @@
Modbus Controller Output
========================
.. seo::
:description: Instructions for setting up a modbus_controller device sensor.
:image: modbus_controller.png
The ``modbus_controller`` platform creates a output from a modbus_controller.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- **address**: (**Required**, int): start address of the first register in a range
- **value_type**: (**Required**): datatype of the mod_bus register data. The default data type for modbus is a 16 bit integer in big endian format (MSB first)
- U_WORD (unsigned float from 1 register =16bit
- S_WORD (signed float from one register)
- U_DWORD (unsigned float from 2 registers = 32bit)
- S_DWORD (unsigned float from 2 registers = 32bit)
- U_DWORD_R (unsigend float from 2 registers low word first )
- S_DWORD_R (sigend float from 2 registers low word first )
- U_QWORD (unsigned float from 4 registers = 64bit
- S_QWORD (signed float from 4 registers = 64bit
- U_QWORD_R (unsigend float from 4 registers low word first )
- S_QWORD_R (sigend float from 4 registers low word first )
- **register_count**: (*Optional*): only required for uncommon response encodings
The number of registers this data point spans. Default is 1
- **write_lambda** (*Optional*, :ref:`lambda <config-lambda>`):
Lambda is evaluated before the modbus write command is created. The value is passed in as `float x` and an empty vector is passed in as `std::vector<uint16_t>&payload`
You can directly define the payload by adding data to payload then the return value is ignored and the content of payload is used.
- **multiply** (*Optional*, float): multiply the new value with this factor before sending the requests. Ignored if lambda is defined.
- **offset**: (*Optional*, int): only required for uncommon response encodings
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
All other options from :ref:`Output <config-output>`.
**Parameters passed into the lambda**
- **x** (float): The float value to be sent to the modbus device
- **payload** (`std::vector<uint16_t>&payload`): empty vector for the payload. The lamdba can add 16 bit raw modbus register words.
note: because the response contains data for all registers in the same range you have to use `data[item->offset]` to get the first response byte for your sensor.
- **item** (const pointer to a SensorItem derived object): The sensor object itself.
Possible return values for the lambda:
- ``return <FLOATING_POINT_NUMBER>;`` the new value for the sensor.
- ``return <anything>; and fill payload with data`` if the payload is added from the lambda then these 16 bit words will be sent
- ``return {};`` if you don't want write the command to the device (or do it from the lambda).
**Example**
.. code-block:: yaml
output:
- platform: modbus_controller
modbus_controller_id: epever
id: battery_capacity_output
lambda: |-
ESP_LOGD("main","Modbus Output incoming value = %f",x);
uint16_t b_capacity = x ;
payload.push_back(b_capacity);
return x * 1.0 ;
address: 0x9001
value_type: U_WORD
See Also
--------
- :doc:`/components/modbus_controller`
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- :doc:`/components/number/modbus_controller`
- https://www.modbustools.com/modbus.html
- :ghedit:`Edit`

View File

@ -0,0 +1,138 @@
Modbus Sensor
=============
.. seo::
:description: Instructions for setting up a modbus_controller device sensor.
:image: modbus.png
The ``modbus_controller`` sensor platform creates a sensor from a modbus_controller component
and requires :doc:`/components/modbus_controller` to be configured.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- **register_type** (**Required**): type of the modbus register.
- coil: coils are also called discrete outout. Coils are 1-bit registers (on/off values) that are used to control discrete outputs. Read and Write access
- discrete_input: discrete input register (read only coil) are similar to coils but can only be read.
- holding: Holding Registers - Holding registers are the most universal 16-bit register. Read and Write access
- read: Read Input Registers - registers are 16-bit registers used for input, and may only be read
- **address**: (**Required**, int): start address of the first register in a range
- **value_type**: (**Required**): datatype of the mod_bus register data. The default data type for modbus is a 16 bit integer in big endian format (MSB first)
- U_WORD (unsigned float from 1 register =16bit
- S_WORD (signed float from one register)
- U_DWORD (unsigned float from 2 registers = 32bit)
- S_DWORD (unsigned float from 2 registers = 32bit)
- U_DWORD_R (unsigend float from 2 registers low word first )
- S_DWORD_R (sigend float from 2 registers low word first )
- U_QWORD (unsigned float from 4 registers = 64bit
- S_QWORD (signed float from 4 registers = 64bit
- U_QWORD_R (unsigend float from 4 registers low word first )
- S_QWORD_R (sigend float from 4 registers low word first )
- **bitmask**: (*Optional*) some values are packed in a response. The bitmask can be used to extract a value from the response. For example, if the high byte value register 0x9013 contains the minute value of the current time. To only exctract this value use bitmask: 0xFF00. The result will be automatically right shifted by the number of 0 before the first 1 in the bitmask. For 0xFF00 (0b1111111100000000) the result is shifted 8 posistions. More than one sensor can use the same address/offset if the bitmask is different.
- **skip_updates**: (*Optional*, integer): By default all sensors of of a modbus_controller are updated together. For data points that don't change very frequently updates can be skipped. A value of 5 would only update this sensor range in every 5th update cycle
Note: The modbus_controller groups component by address ranges to reduce number of transactions. All compoents with the same address will be updated in one request. skip_updates applies for all components in the same range.
- **register_count**: (*Optional*): only required for uncommon response encodings
The number of registers this data point spans. Default is 1
- **force_new_range**: (*Optional*, boolean): If possible sensors with sequential addresses are grouped together and requested in one range. Setting `foce_new_range: true` enforces the start of a new range at that address.
- **lambda** (*Optional*, :ref:`lambda <config-lambda>`):
Lambda to be evaluated every update interval to get the new value of the sensor.
- **offset**: (*Optional*, int): only required for uncommon response encodings
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
For coil or discrete input registers offset is the position of the coil/register because these registers encode 8 coils in one byte.
- All other options from :ref:`Sensor <config-sensor>`.
This example will send 2 modbus commands (device address 1 assumed)
0x1 0x4 0x31 0x0 0x0 0x02 x7f 0x37 ( read 2 registers starting at 0x3100)
0x1 0x3 0x90 0x1 0x0 0x1 0xf8 0xca ( read 1 holding resister from 0x9001 )
.. code-block:: yaml
- platform: modbus_controller
modbus_controller_id: traceran
id: pv_input_voltage
name: "PV array input voltage"
address: 0x3100
unit_of_measurement: "V" ## for any other unit the value is returned in minutes
register_type: read
value_type: U_WORD
accuracy_decimals: 1
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: traceran
id: pv_input_current
name: "PV array input current"
address: 0x3101
unit_of_measurement: "A" ## for any other unit the value is returned in minutes
register_type: read
value_type: U_WORD
accuracy_decimals: 2
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: traceran
name: "Battery Capacity"
id: battery_capacity
register_type: holding
address: 0x9001
unit_of_measurement: "AH"
value_type: U_WORD
The ``modbus`` sensor platform allows you use a lambda that gets called before data is published
using :ref:`lambdas <config-lambda>`.
This example logs the value as parsed and the raw modbus bytes received for this register range
.. code-block:: yaml
# Example configuration entry
sensor:
- platform: modbus_controller
modbus_controller_id: epever
id: battery_capacity
address: 0x9001
name: "Battery Capacity"
register_type: holding
value_type: U_WORD
lambda: |-
ESP_LOGI("","Lambda incoming value=%f - data array size is %d",x,data.size());
ESP_LOGI("","Sensor properties: adress = 0x%X, offset = 0x%X value type=%d",item->start_address,item->offset,item->sensor_value_type);
int i=0 ;
for (auto val : data) {
ESP_LOGI("","data[%d]=0x%02X (%d)",i++ ,data[i],data[i]);
}
return x ;
Parameters passed into the lambda
- **x** (float): The parsed float value of the modbus data
- **data** (std::vector<uint8_t): vector containing the complete raw modbus response bytes for this sensor
note: because the response contains data for all registers in the same range you have to use `data[item->offset]` to get the first response byte for your sensor.
- **item** (const pointer to a SensorItem derived object): The sensor object itself.
Possible return values for the lambda:
- ``return <FLOATING_POINT_NUMBER>;`` the new value for the sensor.
- ``return NAN;`` if the state should be considered invalid to indicate an error (advanced).
See Also
--------
- :doc:`/components/modbus_controller`
- :doc:`/components/number/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- :doc:`EPEVER MPPT Solar Charge Controller Tracer-AN Series</cookbook/tracer-an>`
- :ghedit:`Edit`

View File

@ -0,0 +1,54 @@
Modbus Switch
=============
.. seo::
:description: Instructions for setting up a modbus_controller device sensor.
:image: modbus_controller.png
The ``modbus_controller`` sensor platform creates a sensor from a modbus_controller component
and requires :doc:`/components/modbus_controller` to be configured.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- **modbus_functioncode** (**Required**): type of the modbus register.
- **address**: (**Required**, integer): start address of the first register in a range
- **offset**: (*Optional*, integer): not required in most cases
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
The value for offset depends on the register type. For holding input registers the offset is in bytes. For coil and discrete input resisters the LSB of the first data byte contains the coil addressed in the request. The other coils follow toward the high-order end of this byte and from low order to high order in subsequent bytes. For the registers offset is the position of the relevant bit.
To get the value of the coil register 2 can be retrived using address: 2 / offset: 0 or address: 0 / offset 2
- **bitmask** : some values are packed in a response. The bitmask is used to determined if the result is true or false
- **skip_updates**: (*Optional*, integer): By default all sensors of of a modbus_controller are updated together. For data points that don't change very frequently updates can be skipped. A value of 5 would only update this sensor range in every 5th update cycle
**Example**
.. code-block:: yaml
switch:
- platform: modbus_controller
modbus_controller_id: epever
id: enable_load_test
register_type: coil
address: 2
name: "enable load test mode"
bitmask: 1
Since offset is not zero the read command is part of a range and will be parsed when the range is updated.
The write command to be constructed uses the function code to determine the write command. For a coil it is write single coil.
Because the write command only touches one register start_address and offset have to be corrected.
The final command will be write_single_coil address 5 (start_address+offset) value 1 or 0
For holding registers the write command will be write_single_register. Because the offset for holding registers is given in bytes and the size of a register is 16 bytes the start_address is calculated as start_address + offset/2
See Also
--------
- :doc:`/components/modbus_controller`
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/text_sensor/modbus_controller`
- https://www.modbustools.com/modbus.html
- :ghedit:`Edit`

View File

@ -0,0 +1,115 @@
Modbus Text Sensor
==================
.. seo::
:description: Instructions for setting up a modbus_controller modbus text sensor.
:image: modbus_controller.png
The ``modbus_controller`` sensor platform creates a text sensor from a modbus_controller component
and requires :doc:`/components/modbus_controller` to be configured.
Configuration variables:
------------------------
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
- **name** (**Required**, string): The name of the sensor.
- **register_type** (**Required**): type of the modbus register.
- coil: coils are also called discrete outout. Coils are 1-bit registers (on/off values) that are used to control discrete outputs. Read and Write access
- discrete_input: discrete input register (read only coil) are similar to coils but can only be read.
- holding: Holding Registers - Holding registers are the most universal 16-bit register. Read and Write access
- read: Read Input Registers - registers are 16-bit registers used for input, and may only be read
- **address**: (**Required**, integer): start address of the first register in a range
- **bitmask**: (*Optional*) some values are packed in a response. The bitmask can be used to extract a value from the response. For example, if the high byte value register 0x9013 contains the minute value of the current time. To only exctract this value use bitmask: 0xFF00. The result will be automatically right shifted by the number of 0 before the first 1 in the bitmask. For 0xFF00 (0b1111111100000000) the result is shifted 8 posistions. More than one sensor can use the same address/offset if the bitmask is different.
- **skip_updates**: (*Optional*, integer): By default all sensors of of a modbus_controller are updated together. For data points that don't change very frequently updates can be skipped. A value of 5 would only update this sensor range in every 5th update cycle
- **register_count**: (*Optional*): The number of registers this data point spans. Default is 1
- **response_size**: (**Required**):response number of bytes of the response
- **raw_encode**: (*Optional*, NONE , HEXBYTES, COMMA) If the response is binary it can't be published directly. Since a text sensor only publishes strings the binary data can encoded
- HEXBYTES: 2 byte hex string. 0x2011 will be sent as "2011".
- COMMA: Byte values as integers, delimited by a coma. 0x2011 will be sent as "32,17"
- **force_new_range**: (*Optional*, boolean): If possible sensors with sequential addresses are grouped together and requested in one range. Setting `foce_new_range: true` enforces the start of a new range at that address.
- **lambda** (*Optional*, :ref:`lambda <config-lambda>`):
Lambda to be evaluated every update interval to get the new value of the sensor
- **offset**: (*Optional*, integer): not required in most cases
offset from start address in bytes. If more than one register is read a modbus read registers command this value is used to find the start of this datapoint relative to start address. The component calculates the size of the range based on offset and size of the value type
Parameters passed into the lambda
- **x** (std:string): The parsed float value of the modbus data
- **data** (std::vector<uint8_t): vector containing the complete raw modbus response bytes for this sensor
note: because the response contains data for all registers in the same range you have to use `data[item->offset]` to get the first response byte for your sensor.
- **item** (const pointer to a SensorItem derived object): The sensor object itself.
Possible return values for the lambda:
- ``return <FLOATING_POINT_NUMBER>;`` the new value for the sensor.
- ``return NAN;`` if the state should be considered invalid to indicate an error (advanced).
- ``return {};`` if you don't want to publish a new state (advanced).
**Example**
.. code-block:: yaml
text_sensor:
- platform: template
name: "RTC Time Sensor"
id: template_rtc
- platform: modbus_controller
modbus_controller_id: traceranx
name: "rtc clock test"
id: rtc_clock_test
internal: true
register_type: holding
address: 0x9013
register_count: 3
hex_encode: true
response_size: 6
on_value:
then:
- lambda: |-
ESP_LOGV("main", "decoding rtc hex encoded raw data: %s", x.c_str());
uint8_t h=0,m=0,s=0,d=0,month_=0,y = 0 ;
m = esphome::modbus_controller::byte_from_hex_str(x,0);
s = esphome::modbus_controller::byte_from_hex_str(x,1);
d = esphome::modbus_controller::byte_from_hex_str(x,2);
h = esphome::modbus_controller::byte_from_hex_str(x,3);
y = esphome::modbus_controller::byte_from_hex_str(x,4);
month_ = esphome::modbus_controller::byte_from_hex_str(x,5);
// Now check if the rtc time of the controller is ok and correct it
time_t now = ::time(nullptr);
struct tm *time_info = ::localtime(&now);
int seconds = time_info->tm_sec;
int minutes = time_info->tm_min;
int hour = time_info->tm_hour;
int day = time_info->tm_mday;
int month = time_info->tm_mon + 1;
int year = time_info->tm_year - 2000;
// correct time if needed (ignore seconds)
if (d != day || month_ != month || y != year || h != hour || m != minutes) {
// create the payload
std::vector<uint16_t> rtc_data = {uint16_t((minutes << 8) | seconds), uint16_t((day << 8) | hour),
uint16_t((year << 8) | month)};
// Create a modbus command item with the time information as the payload
esphome::modbus_controller::ModbusCommandItem set_rtc_command = esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(traceranx, 0x9013, 3, rtc_data);
// Submit the command to the send queue
traceranx->queue_command(set_rtc_command);
ESP_LOGI("ModbusLambda", "EPSOLAR RTC set to %02d:%02d:%02d %02d.%02d.%04d", hour, minutes, seconds, day, month, year + 2000);
}
char buffer[20];
// format time as YYYY:mm:dd hh:mm:ss
sprintf(buffer,"%04d:%02d:%02d %02d:%02d:%02d",y+2000,month_,d,h,m,s);
id(template_rtc).publish_state(buffer);
See Also
--------
- :doc:`/components/modbus_controller`
- :doc:`/components/sensor/modbus_controller`
- :doc:`/components/binary_sensor/modbus_controller`
- :doc:`/components/switch/modbus_controller`
- https://www.modbustools.com/modbus.html
- :ghedit:`Edit`

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

1290
cookbook/tracer-an.rst Normal file

File diff suppressed because it is too large Load Diff

BIN
images/esp32-modbus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
images/modbus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/rs485.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
images/tracer-an.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -319,6 +319,7 @@ Miscellaneous
b-parasite, components/sensor/b_parasite, b_parasite.jpg, Moisture & Temperature & Humidity & Light
EZO sensor circuits, components/sensor/ezo, ezo-ph-circuit.png, (pH)
Havells Solar, components/sensor/havells_solar, havellsgti5000d_s.jpg, Solar rooftop
Modbus Sensor, components/sensor/modbus_controller, modbus.png
Nextion, components/sensor/nextion, nextion.jpg, Sensors from display
Rotary Encoder, components/sensor/rotary_encoder, rotary_encoder.jpg
Tuya Sensor, components/sensor/tuya, tuya.png
@ -376,6 +377,7 @@ Binary Sensor Components
RDM6300, components/binary_sensor/rdm6300, rdm6300.jpg
TTP229, components/binary_sensor/ttp229, ttp229.jpg
Tuya Binary Sensor, components/binary_sensor/tuya, tuya.png
Modbus Binary Sensor, components/binary_sensor/modbus_controller, modbus.png
XPT2046, components/binary_sensor/xpt2046, xpt2046.jpg
Custom Binary Sensor, components/binary_sensor/custom, language-cpp.svg
@ -398,6 +400,7 @@ Output Components
SM16716, components/output/sm16716, sm16716.svg
SM2135, components/output/sm2135, sm2135.svg
MCP4725, components/output/mcp4725, mcp4725.jpg
Modbus Output, components/output/modbus_controller, modbus.png
Custom Output, components/output/custom, language-cpp.svg
Template Output, components/output/template, description.svg
@ -444,6 +447,7 @@ Switch Components
UART Switch, components/switch/uart, uart.svg
Custom Switch, components/switch/custom, language-cpp.svg
Tuya Switch, components/switch/tuya, tuya.png
Modbus Switch, components/switch/modbus_controller, modbus.png
BLE Client Switch, components/switch/ble_client, bluetooth.svg
Nextion Switch, components/switch/nextion, nextion.jpg
@ -507,6 +511,7 @@ Text Sensor Components
Version, components/text_sensor/version, new-box.svg
WiFi Info, components/text_sensor/wifi_info, network-wifi.svg
BLE Scanner, components/text_sensor/ble_scanner, bluetooth.svg
Modbus Text Sensor, components/text_sensor/modbus_controller, modbus.png
Template Text Sensor, components/text_sensor/template, description.svg
Custom Text Sensor, components/text_sensor/custom, language-cpp.svg
Nextion Text Sensor, components/text_sensor/nextion, nextion.jpg
@ -532,6 +537,7 @@ Number Components
.. imgtable::
Number Core, components/number/index, folder-open.svg
Modbus Number, components/number/modbus_controller, modbus.png
Template Number, components/number/template, description.svg
Select Components
@ -579,6 +585,7 @@ Misc Components
TM1651 Battery Display, components/tm1651, tm1651_battery_display.jpg
RF Bridge, components/rf_bridge, rf_bridge.jpg
Tuya MCU, components/tuya, tuya.png
Modbus Controller, components/modbus_controller, modbus.png
Exposure Notifications, components/exposure_notifications, exposure_notifications.png
RTTTL Buzzer, components/rtttl, buzzer.jpg
Prometheus, components/prometheus, prometheus.svg
@ -628,6 +635,7 @@ Cookbook
IAQ (Indoor Air Quality) Board, cookbook/iaq_board, iaq_board2.jpg
Custom UART Text Sensor, cookbook/uart_text_sensor, language-cpp.svg
IWOOLE Table Lamp, cookbook/iwoole_rgbw_table_lamp, iwoole_rgbw_table_lamp.png
EPEVER Tracer, cookbook/tracer-an, tracer-an.jpg
Ilonda Wifi Smart Fish Feeder, cookbook/ilonda-wifi-smart-fish-feeder, ilonda-wifi-smart-fish-feeder-cookbook.jpg
Do you have other awesome automations or cool setups? Please feel free to add them to the