As the ESP-IDF Modbus TCP is rather complex I decided to strip it down to a bare minimum.
This implemention is based on ESP-IDF v4.4.1.
It increases an uint16_t datafield on address 1 if the Holding register is getting read.
Findings:
- There aren’t any callback-routines available to update the register values before the actual reading is taking place.
- The event thrown by mbc_slave_check_event() comes AFTER the read/write.
The network interface ip_netif_ptr must me initialized as descriped in protocol_examples_common.
#include <stdio.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "mbcontroller.h"
#include "wifi.h"
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD | MB_EVENT_HOLDING_REG_RD)
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR)
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
#define MB_PAR_INFO_GET_TOUT 10
#define MB_REG_HOLDING_START_AREA0 0
#define MB_REG_HOLD_CNT 1
static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
static const char *TAG = "modbus";
mb_register_area_descriptor_t reg_area;
uint16_t holding_reg_area[MB_REG_HOLD_CNT] = {0};
void modbus_loop()
{
mb_param_info_t reg_info;
while (1)
{
ESP_LOGI(TAG, "Waiting for modbus request");
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
const char *rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
if (event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD))
{
/* This is where the magic happens */
portENTER_CRITICAL(¶m_lock);
holding_reg_area[0]++;
portEXIT_CRITICAL(¶m_lock);
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(uint32_t)reg_info.time_stamp,
(uint32_t)reg_info.mb_offset,
(uint32_t)reg_info.type,
(uint32_t)reg_info.address,
(uint32_t)reg_info.size);
}
}
}
void modbus_init()
{
mb_register_area_descriptor_t reg_area;
void *slave_handler = NULL;
// Initialization of Modbus controller
ESP_ERROR_CHECK(mbc_slave_init_tcp(&slave_handler));
mb_communication_info_t comm_info = {
.ip_port = 502,
.ip_addr_type = MB_IPV4,
.ip_mode = MB_MODE_TCP,
.ip_addr = NULL,
.ip_netif_ptr = (void *)get_example_netif()
};
ESP_ERROR_CHECK(mbc_slave_setup((void *)&comm_info));
reg_area.type = MB_PARAM_HOLDING;
reg_area.start_offset = MB_REG_HOLDING_START_AREA0;
reg_area.address = (void *)&holding_reg_area[0];
reg_area.size = sizeof(holding_reg_area) << 1;
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
ESP_ERROR_CHECK(mbc_slave_start());
}
Running it on a ESP32 results in following logging output when executing a Holding read.
I (11071) MB_TCP_SLAVE_PORT: Protocol stack initialized.
I (11081) MB_TCP_SLAVE_PORT: Socket (#54), listener on port: 502, errno=0
I (202771) MB_TCP_SLAVE_PORT: Socket (#55), accept client connection from address: 192.168.1.46
I (202771) modbus: HOLDING READ (202282472 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb4152, SIZE:1
E (202791) MB_TCP_SLAVE_PORT: Socket (#55)(192.168.1.46), connection closed by peer.
Following Python Script was used to test it.
from pyModbusTCP.client import ModbusClient
c = ModbusClient('192.168.1.45', port=502, auto_open=True, debug=False)
data = c.read_holding_registers(0)
print (data)
Resulting in following console output:
dev@dev:~/esp-idf$ python3 test.py
[0]
dev@dev:~/esp-idf$ python3 test.py
[1]
dev@dev:~/esp-idf$ python3 test.py
[2]
dev@dev:~/esp-idf$ python3 test.py
[3]