Merge branch 'feature/response_time_handler_script_v2' into 'main'

Modify the pytest script to handle response time from the DUT log and multiple  instances of Modbus objects.

See merge request idf/esp-modbus!101
This commit is contained in:
Alex Lisitsyn
2025-11-27 16:01:09 +00:00
51 changed files with 2937 additions and 319 deletions
+1 -2
View File
@@ -216,8 +216,6 @@ build_idf_v5.1:
before_script:
# Install or upgrade pytest-embedded to perform test cases
- pip install -r ${TEST_DIR}/tools/test_requirements.txt
# Upgrade the packages (workaround for esp-idf v5.0)
- pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf --upgrade
.test_cur_folder: &test_cur_folder |
export IDF_VER=$(cat ${TEST_DIR}/idf_version_info.txt)
@@ -250,6 +248,7 @@ build_idf_v5.1:
- "${TEST_DIR}/**/log.html" # robot framework related files
- "${TEST_DIR}/**/report.html"
- "${TEST_DIR}/**/*.pcap"
- "${TEST_DIR}/**/*.png"
when: always
expire_in: 1 week
+510 -36
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
@@ -8,17 +8,21 @@ import os
import sys
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, Match, Optional, TextIO, Tuple
from statistics import mean
from typing import Any, Callable, Dict, Match, Optional, Tuple, List
import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass
import pexpect
import pytest
from pytest_embedded_idf import CaseTester
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
from pytest_embedded_idf.app import IdfApp
from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.serial import IdfSerial
from pytest_embedded_idf import CaseTester
class Stages(Enum):
@@ -30,16 +34,121 @@ class Stages(Enum):
STACK_START = 6
STACK_PAR_OK = 7
STACK_PAR_FAIL = 8
STACK_DESTROY = 9
STACK_OBJECT_CREATE = 9
STACK_DESTROY = 10
STACK_BAD_CONNECTION = 11
STACK_CID_RESPONSE_TIME = 12
## Object Fields
TRANSACTION_TIMESTAMP = 0
OBJ_TAG = 1
OBJ_ID = 2
## Parameters Fields
OBJ_ADDRESS = 1
CID = 2
PARAM_NAME = 3
PARAM_SUCCESS = "success"
PARAM_FAIL = "fail"
## Getter tags for readability
MASTER_TAG = "master"
SLAVE_TAG = "slave"
DEFAULT_SDKCONFIG = "default"
ALLOWED_PERCENT_OF_FAILS = 10
## Dataclass for structuring data to plot statistical graph
@dataclass
class ModbusDutStats:
success_stats: List[int]
fail_stats: List[int]
class MbRequestResponse:
def __init__(self, transaction_timestamp, master_inst_address, cid) -> None:
self.transaction_timestamp: int = transaction_timestamp
self.master_inst_address: bytes = master_inst_address
self.cid: bytes = cid
class MbParameter:
def __init__(
self,
name,
instance_address,
transaction_timestamp,
obj_tag,
status,
cid,
response_time,
) -> None:
self.name: str = name
self.instance_address: bytes = instance_address
self.transaction_timestamp: int = transaction_timestamp
self.obj_tag: str = obj_tag
self.status: str = status
self.cid: bytes = cid
self.response_time: Optional[int] = response_time
def __repr__(self) -> str:
if self.obj_tag == MASTER_TAG:
return f"Parameter name:{self.name}, Obj type: {self.obj_tag}, Object ID:{self.instance_address!r}, Transaction time:{self.transaction_timestamp}, Status:{self.status}, Cid:{self.cid}, Master Response time:{self.response_time}"
else:
return f"Parameter name:{self.name}, Obj type: {self.obj_tag}, Object ID:{self.instance_address}, Transaction time:{self.transaction_timestamp}, Status:{self.status}"
class MbObject:
def __init__(self, tag, id, object_creation_timestamp) -> None:
self.tag: str = tag
self.id: bytes = id
self.object_creation_timestamp: bytes = object_creation_timestamp
self.parameters: List[MbParameter] = []
self.parameter_count: int = 0
def __repr__(self):
return f"Obj Tag: {self.tag}, Obj ID: {self.id}, Creation Timestamp: {self.object_creation_timestamp}"
def add_parameter(
self,
name: bytes,
instance_address: bytes,
transaction_timestamp: bytes,
tag: str,
status: str,
cid: bytes,
) -> MbParameter:
"""The function add to list master or slave parameters"""
parameter = MbParameter(
name,
instance_address,
int(transaction_timestamp.decode("ascii")),
tag,
status,
cid,
None,
)
self.parameters.append(parameter)
self.parameter_count += 1
return parameter
def is_master(self) -> bool:
"""The function checks for master tag in instance object"""
if MASTER_TAG in self.tag:
return True
else:
return False
def is_slave(self) -> bool:
"""The function checks for slave tag in instance object"""
if SLAVE_TAG in self.tag:
return True
else:
return False
class ModbusTestDut(IdfDut):
TEST_IP_PROMPT = r"Waiting IP([0-9]{1,2}) from stdin:\r\r\n"
TEST_IP_SET_CONFIRM = r".*IP\([0-9]+\) = \[([0-9a-zA-Z\.\:]+)\] set from stdin.*"
TEST_IP_ADDRESS_REGEXP = r".*example_[a-z]+: .* IPv4 [a-z]+:.* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*"
TEST_APP_NAME = r"I \([0-9]+\) [a-z_]+: Project name:\s+([_a-z]*)"
@@ -53,20 +162,200 @@ class ModbusTestDut(IdfDut):
def __init__(self, *args, **kwargs) -> None: # type: ignore
super().__init__(*args, **kwargs)
self.logger = logging.getLogger()
self.test_output: Optional[TextIO] = None
self.ip_address: Optional[str] = None
self.app_name: Optional[str] = None
self.param_fail_count = 0
self.param_ok_count = 0
self.dut_list: Optional[List[ModbusTestDut]] = None
self.param_fail_count: int = 0
self.param_ok_count: int = 0
self.test_stage = Stages.STACK_DEFAULT
self.dictionary = None
self.test_finish = False
self.test_status = False
self.test_finish: bool = False
self.mb_objects_count: int = (
0 # number of objects (modbus instances in the test application)
)
self.mb_objects: List[MbObject] = []
self.mb_request_response: List[MbRequestResponse] = []
def close(self) -> None:
super().close()
def check_mb_objects_list(self) -> None:
"""Method to check if mb_objects list is not empty"""
if not self.mb_objects:
self.logger.error("list of modbus objects in DUT couldn't be retrieved")
raise RuntimeError from None
for objects in self.mb_objects:
if objects is None:
self.logger.error("list of modbus objects in DUT is wrongly populated")
raise RuntimeError from None
return None
def add_object(self, tag: str, id: bytes, timestamp: bytes) -> MbObject:
"""The function add to list master or slave instances in the test"""
obj = MbObject(tag, id, timestamp)
self.mb_objects.append(obj)
self.mb_objects_count += 1
return obj
def get_objects_by_tag(self, tag: str) -> List[MbObject]:
"""The getter retrieves object by master or slave tag"""
objects: List[MbObject] = []
self.check_mb_objects_list()
for object in self.mb_objects:
if tag == object.tag:
objects.append(object)
if not objects:
self.logger.error("objects list from tag couldn't be retrieved")
raise RuntimeError from None
else:
return objects
def get_object_by_id(self, id: bytes) -> MbObject:
"""The getter retrieves master or slave object by instance address"""
self.check_mb_objects_list()
for object in self.mb_objects:
if id == object.id:
return object
self.logger.error("couldn't find object by id")
raise RuntimeError from None
def get_params_by_object_id_and_status(
self, instance_address: bytes, status: str
) -> Optional[List[MbParameter]]:
"""The getter retrieves parameters by instance address of object and status of request"""
self.check_mb_objects_list()
for obj in self.mb_objects:
if obj.id == instance_address:
return [
param
for param in obj.parameters
if param.instance_address == instance_address
and param.status == status
]
return None
def get_master_params_by_status(self, status: str) -> List[MbParameter]:
"""The getter retrieves master parameters by status request"""
all_master_params: List[MbParameter] = []
self.check_mb_objects_list()
for obj in self.mb_objects:
if obj.is_master():
all_master_params.extend(
[
param
for param in obj.parameters
if param.status == status and param.obj_tag == obj.tag
]
)
return all_master_params
def get_slave_params_by_status(self, status: str) -> List[MbParameter]:
"""The getter retrieves slave parameters by status of request"""
all_slave_params: List[MbParameter] = []
self.check_mb_objects_list()
for obj in self.mb_objects:
if obj.is_slave():
all_slave_params.extend(
[
param
for param in obj.parameters
if param.status == status and param.obj_tag == obj.tag
]
)
return all_slave_params
def get_params_by_timestamp_range(
self, start: int, finish: int
) -> List[MbParameter]:
"""The getter retrieves parameters by timestamp"""
params_by_timestamp: List[MbParameter] = []
self.check_mb_objects_list()
for obj in self.mb_objects:
if obj is not None:
params_by_timestamp.extend(
[
param
for param in obj.parameters
if start <= param.transaction_timestamp <= finish
]
)
return params_by_timestamp
def add_dut_list(self, dut_instance: "ModbusTestDut") -> None:
"""The function keeps track of all DUTs involved in the test"""
if self.dut_list is None:
self.dut_list: List[ModbusTestDut] = []
self.dut_list.append(dut_instance)
return None
def send_message_destroy_other_dut_instances(self) -> None:
"""The function sends message to end all DUTs"""
if self.dut_list is not None:
for dut_instance in self.dut_list:
self.logger.info("Sending destroy message to others DUT Instances")
dut_instance.write("Destroy instances\n")
return None
def get_avg_response_time_master(self) -> int:
"""The function iterates over master parameters to calculate mean response time"""
master_success_params = self.get_master_params_by_status(PARAM_SUCCESS)
if master_success_params:
avg_response_time = [
param.response_time
for param in master_success_params
if param.response_time is not None
]
if avg_response_time:
return int(mean(avg_response_time))
self.logger.info("Master Response time list couldn't be properly retrieved")
return 0
def send_message_destroy_dut(self) -> None:
"""The function sends message to end caller DUT"""
self.logger.info("Sending destroy message to this DUT")
self.write("Destroy instances\n")
return None
def add_request_response(
self, transaction_timestamp: bytes, master_address: bytes, cid: bytes
) -> None:
"""The function gets and saves master requests"""
request_response = MbRequestResponse(
int(transaction_timestamp.decode("ascii")), master_address, cid
)
self.mb_request_response.append(request_response)
return None
def add_response_time_to_param(self, param: MbParameter) -> None:
"""The function add response time to parameter entry based on request list"""
last_request_response = self.mb_request_response[-1]
if (
param.instance_address == last_request_response.master_inst_address
and param.cid == last_request_response.cid
and param.transaction_timestamp
>= last_request_response.transaction_timestamp
):
param.response_time = (
param.transaction_timestamp
- last_request_response.transaction_timestamp
)
self.logger.info(
"Response time:%d for Cid:%s from Master:%s",
param.response_time,
param.cid,
param.instance_address,
)
return None
def dut_get_ip(self) -> Optional[str]:
"""The function gets IP address from log"""
if self.ip_address is None:
expect_address = self.expect(
self.TEST_IP_ADDRESS_REGEXP, timeout=self.TEST_EXPECT_STR_TIMEOUT
@@ -76,6 +365,7 @@ class ModbusTestDut(IdfDut):
return self.ip_address
def dut_get_name(self) -> Optional[str]:
"""The function gets project name from log"""
if self.app_name is None:
expect_name = self.expect(
self.TEST_APP_NAME, timeout=self.TEST_EXPECT_STR_TIMEOUT
@@ -86,7 +376,7 @@ class ModbusTestDut(IdfDut):
def dut_send_ip(self, slave_ip: Optional[str]) -> Optional[int]:
"""The function sends the slave IP address defined as a parameter to master"""
addr_num = 0
addr_num: int = 0
self.expect(self.TEST_IP_PROMPT, timeout=self.TEST_ACK_TIMEOUT)
if isinstance(slave_ip, str):
for addr_num in range(0, self.TEST_MAX_CIDS):
@@ -96,10 +386,11 @@ class ModbusTestDut(IdfDut):
return addr_num
def get_expect_proc(self) -> Optional[object]:
"""The function is a workaround to use expect method"""
expect_proc: object = None
try:
expect_proc = self.__getattribute__("pexpect_proc")
except AttributeError:
except:
expect_proc = self.__getattribute__("_p")
finally:
if expect_proc and callable(getattr(expect_proc, "expect")):
@@ -107,10 +398,108 @@ class ModbusTestDut(IdfDut):
else:
return None
def expect_any(
def dut_stats_info(self) -> ModbusDutStats:
"""The function retrieves all success and fail parameters per DUT to plot stats graph"""
all_success_params: List[int] = []
all_fail_params: List[int] = []
self.check_mb_objects_list()
for obj in self.mb_objects:
temp_success_params: Optional[List[MbParameter]] = (
self.get_params_by_object_id_and_status(obj.id, PARAM_SUCCESS)
)
if temp_success_params is not None:
all_success_params.append(len(temp_success_params))
else:
self.logger.error(
"Couldn't retrieve success parameters for object %s", obj.id
)
all_success_params.append(
0
) # propagating the None error to raise exception
temp_fail_params: Optional[List[MbParameter]] = (
self.get_params_by_object_id_and_status(obj.id, PARAM_FAIL)
)
if temp_fail_params is not None:
all_fail_params.append(len(temp_fail_params))
else:
self.logger.error("Fail parameters couldn't be retrieved to plot graph")
raise RuntimeError from None
if (
all(all_success_params) is False or not all_success_params
): # Checking only success parameters. Slaves dont have fail parameters, they return a zero list.
self.logger.error("success parameters couldn't be retrieved to plot graph")
raise RuntimeError from None
else:
return ModbusDutStats(all_success_params, all_fail_params)
def plot_modbus_stats(
self,
*expect_items: Tuple[str, Callable],
timeout: Optional[int] = TEST_EXPECT_STR_TIMEOUT,
master_objects: List[MbObject],
master_all_params: ModbusDutStats,
slave_objects: List[MbObject],
slave_all_params: ModbusDutStats,
file_name: str,
) -> None:
"""The function plots graph from all success and fail parameters"""
names: List[str] = []
width = 0.1
alpha = 0.5
right = 0.55
x_position_text = 1.03
y_position_text = 0.98
label_space = 0.3
for obj in master_objects:
names.append(f"Master \n {obj.id.decode()}")
for obj in slave_objects:
names.append(f"Slave \n {obj.id.decode()}")
label_axis = np.arange(len(names)) * label_space
text_box = (
f"Master Average response time:{self.get_avg_response_time_master()} ms"
)
fig, ax = plt.subplots()
plt.bar(
label_axis,
(master_all_params.success_stats + slave_all_params.success_stats),
width=width,
color="green",
label="Success",
)
plt.bar(
(label_axis + width),
(master_all_params.fail_stats + slave_all_params.fail_stats),
width=width,
color="red",
label="Fail",
)
plt.xticks(label_axis, names)
props = dict(boxstyle="round", facecolor="wheat", alpha=alpha)
ax.text(
x_position_text,
y_position_text,
text_box,
transform=ax.transAxes,
fontsize=12,
verticalalignment="top",
bbox=props,
)
plt.subplots_adjust(right=right)
plt.legend()
plt.title("Modbus Test Statistics")
plt.ylabel("Parameters accessed")
plt.savefig(f"{file_name}.png")
plt.show()
def expect_any(
self, *expect_items: Tuple[str, Callable], timeout: Optional[int]
) -> None:
"""
expect_any(*expect_items, timeout=DEFAULT_TIMEOUT)
@@ -150,76 +539,153 @@ class ModbusTestDut(IdfDut):
expect_proc: Optional[object] = self.get_expect_proc()
if expect_proc is not None:
match_index = expect_proc.expect(expect_patterns, timeout) # type: ignore [attr-defined]
match_index = expect_proc.expect(expect_patterns, timeout)
if isinstance(match_index, int):
match_item = expect_items_list[match_index] # type: ignore [attr-defined]
match_item["index"] = match_index
match_item = expect_items_list[match_index] # type: ignore
match_item["index"] = match_index # type: ignore
if (
isinstance(expect_proc.match, Match) # type: ignore [attr-defined]
and len(expect_proc.match.groups()) > 0 # type: ignore [attr-defined]
isinstance(expect_proc.match, Match)
and len(expect_proc.match.groups()) > 0
):
match_item["ret"] = expect_proc.match.groups() # type: ignore [attr-defined]
match_item["ret"] = expect_proc.match.groups()
if match_item["callback"]:
match_item["callback"](
match_item["ret"]
) # execution of callback function
else:
self.logger.error(
"%s: failed to parse output. Please check component versions.",
"%s: failed to parse output. Please check component versions",
self.app_name,
)
raise RuntimeError from None
def dut_test_start(
self, dictionary: Dict, timeout_value: Optional[int] = TEST_EXPECT_STR_TIMEOUT
self, dictionary: Dict, timeout_value=TEST_EXPECT_STR_TIMEOUT
) -> None: # type: ignore
"""The method to initialize and handle test stages"""
def handle_get_ip4(data: Optional[Any]) -> None:
def handle_get_ip4(data: Any) -> None:
"""Handle get_ip v4"""
self.logger.info("%s[STACK_IPV4]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_IPV4
# Synch for handling the case where the DUT dont stablish connection from the beginning
# DUT reboot after failing reconnecting
if self.test_stage.value >= Stages.STACK_IPV4.value:
self.logger.info(
"%s: Reboot loop detected on stage [STACK_IPV4], ending test.",
self.app_name,
)
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
self.test_finish = True
else:
self.logger.info("%s[STACK_IPV4]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_IPV4
def handle_get_ip6(data: Optional[Any]) -> None:
def handle_get_ip6(data: Any) -> None:
"""Handle get_ip v6"""
self.logger.info("%s[STACK_IPV6]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_IPV6
def handle_init(data: Optional[Any]) -> None:
def handle_init(data: Any) -> None:
"""Handle init"""
self.logger.info("%s[STACK_INIT]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_INIT
def handle_connect(data: Optional[Any]) -> None:
def handle_connect(data: Any) -> None:
"""Handle connect"""
self.logger.info("%s[STACK_CONNECT]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_CONNECT
def handle_test_start(data: Optional[Any]) -> None:
def handle_test_start(data: Any) -> None:
"""Handle connect"""
self.logger.info("%s[STACK_START]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_START
def handle_par_ok(data: Optional[Any]) -> None:
def handle_cid_response_time(data: Any) -> None:
"""Handle Cid sent request for response time calculation"""
self.logger.info(
"%s[STACK_CID_RESPONSE_TIME]: %s", self.app_name, str(data)
)
self.test_stage = Stages.STACK_CID_RESPONSE_TIME
self.add_request_response(
data[TRANSACTION_TIMESTAMP], data[OBJ_ADDRESS], data[CID]
)
def handle_bad_connection(data: Any) -> None:
"""Handle bad connection"""
self.logger.info(
"%s Reached the stage [STACK_BAD_CONNECTION]. Ending the test.",
self.app_name,
)
self.test_stage = Stages.STACK_BAD_CONNECTION
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
self.test_finish = True
def handle_par_ok(data: Any) -> None:
"""Handle parameter ok"""
self.logger.info("%s[READ_PAR_OK]: %s", self.app_name, str(data))
if self.test_stage.value >= Stages.STACK_START.value:
self.param_ok_count += 1
# Checking if MBobject exist in the object list
object_handle = self.get_object_by_id(data[OBJ_ADDRESS])
last_sucess_parameter = object_handle.add_parameter(
data[PARAM_NAME],
data[OBJ_ADDRESS],
data[TRANSACTION_TIMESTAMP],
object_handle.tag,
PARAM_SUCCESS,
data[CID],
)
if object_handle.is_master():
self.add_response_time_to_param(
last_sucess_parameter
) # adding to parameter the response time for this request
self.logger.info(last_sucess_parameter)
else:
self.logger.info(last_sucess_parameter)
self.param_ok_count += 1
self.test_stage = Stages.STACK_PAR_OK
def handle_par_fail(data: Optional[Any]) -> None:
def handle_par_fail(data: Any) -> None:
"""Handle parameter fail"""
self.logger.info("%s[READ_PAR_FAIL]: %s", self.app_name, str(data))
# Checking if MBobject exist in the object list
object_handle = self.get_object_by_id(data[OBJ_ADDRESS])
last_fail_parameter = object_handle.add_parameter(
data[PARAM_NAME],
data[OBJ_ADDRESS],
data[TRANSACTION_TIMESTAMP],
object_handle.tag,
PARAM_FAIL,
data[CID],
)
self.logger.info(last_fail_parameter)
self.param_fail_count += 1
self.test_stage = Stages.STACK_PAR_FAIL
def handle_destroy(data: Optional[Any]) -> None:
def handle_obj_create(data: Any) -> None:
"""Handle creation"""
self.logger.info(
"Object creation handled: %s[%s]: %s",
self.app_name,
Stages.STACK_OBJECT_CREATE.name,
str(data),
)
last_add_object = self.add_object(
data[OBJ_TAG].decode("ascii"), data[OBJ_ID], data[TRANSACTION_TIMESTAMP]
)
self.test_stage = Stages.STACK_OBJECT_CREATE
self.logger.info("New added object: %s", last_add_object)
def handle_destroy(data: Any) -> None:
"""Handle destroy"""
self.logger.info(
"%s[%s]: %s", self.app_name, Stages.STACK_DESTROY.name, str(data)
)
self.test_stage = Stages.STACK_DESTROY
self.send_message_destroy_other_dut_instances()
self.test_finish = True
while not self.test_finish:
@@ -232,7 +698,13 @@ class ModbusTestDut(IdfDut):
(dictionary[Stages.STACK_START], handle_test_start),
(dictionary[Stages.STACK_PAR_OK], handle_par_ok),
(dictionary[Stages.STACK_PAR_FAIL], handle_par_fail),
(dictionary[Stages.STACK_OBJECT_CREATE], handle_obj_create),
(dictionary[Stages.STACK_DESTROY], handle_destroy),
(dictionary[Stages.STACK_BAD_CONNECTION], handle_bad_connection),
(
dictionary[Stages.STACK_CID_RESPONSE_TIME],
handle_cid_response_time,
),
timeout=timeout_value,
)
except pexpect.TIMEOUT:
@@ -242,6 +714,8 @@ class ModbusTestDut(IdfDut):
self.test_stage.name,
timeout_value,
)
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
self.test_finish = True
def dut_check_errors(self) -> None:
@@ -260,7 +734,7 @@ class ModbusTestDut(IdfDut):
)
else:
self.logger.error(
"%s: ok_count: %d, number of failed readings %d exceeds %d percent",
"%s: ok_count: %d, number of failed readings: %d exceeds: %d percent",
self.app_name,
self.param_ok_count,
self.param_fail_count,
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -312,55 +312,60 @@ static void *master_get_param_data(const mb_parameter_descriptor_t *param_descri
return instance_ptr;
}
#define TEST_VERIFY_VALUES(handle, descr, inst) (__extension__( \
{ \
assert(inst); \
assert(descr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, descr->cid, \
(uint8_t *)inst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (descr->param_opts.opt3) { \
for EACH_ITEM(inst, descr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(inst)))descr->param_opts.opt3) { \
*item_ptr = (typeof(*(inst)))descr->param_opts.opt3; \
ESP_LOGD(TAG, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
(int)descr->cid, \
(char *)descr->param_key, \
(uint16_t)descr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
(int)descr->cid, \
(char *)descr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)inst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
(int)descr->cid, \
(char *)descr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
(int)descr->cid, \
(char *)descr->param_key, \
(char *)descr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
(int)descr->cid, \
(char *)descr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
#define TEST_VERIFY_VALUES(handle, pdescr, pinst) (__extension__( \
{ \
assert(pinst); \
assert(pdescr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, pdescr->cid, \
(uint8_t *)pinst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*item_ptr = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "%p Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s), initialize.", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)pinst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (..) write successful.", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(char *)pdescr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
))
// User operation function to read slave values and check alarm
@@ -426,7 +431,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_TEST_REG)) {
// Check test parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -437,7 +443,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_U16_BA)) {
// Check the uint16 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -447,7 +454,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_U8_B)) {
// Check the uint8 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -457,7 +465,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_UINT32_DCBA)) {
// Check the uint32 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -468,7 +477,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_FLOAT_DCBA)) {
// Check the float parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -478,7 +488,8 @@ static void master_operation_func(void *arg)
} else if (param_descriptor->cid >= CID_HOLD_DOUBLE_ABCDEFGH) {
// Check the double parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (double *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -488,7 +499,8 @@ static void master_operation_func(void *arg)
#endif
} else if (cid <= CID_HOLD_DATA_2) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -506,14 +518,16 @@ static void master_operation_func(void *arg)
uint8_t state = *(uint8_t *)temp_data_ptr;
const char *rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
} else {
ESP_LOGE(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
ESP_LOGE(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -629,4 +643,5 @@ void app_main(void)
vTaskDelay(10);
master_operation_func(NULL);
ESP_LOGI(TAG, "Master Serial is completed. (%s).", __func__);
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -306,7 +306,8 @@ void app_main(void)
// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
ESP_LOGI(TAG, "HOLDING %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, HOLDING %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
mbc_slave_handle,
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
@@ -322,21 +323,24 @@ void app_main(void)
(void)mbc_slave_unlock(mbc_slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
mbc_slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "DISCRETE READ (%" PRIu32 " us): ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, DISCRETE READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
mbc_slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
mbc_slave_handle,
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
@@ -350,7 +354,7 @@ void app_main(void)
}
}
// Destroy of Modbus controller on alarm
ESP_LOGI(TAG, "Modbus controller destroyed.");
ESP_LOGI(TAG, "Destroy slave.");
vTaskDelay(100);
ESP_ERROR_CHECK(mbc_slave_delete(mbc_slave_handle));
}
+102 -24
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
@@ -7,59 +7,76 @@
import logging
import os
from typing import Tuple
import pytest
from conftest import ModbusTestDut, Stages
from conftest import (
ModbusTestDut,
Stages,
PARAM_SUCCESS,
PARAM_FAIL,
MASTER_TAG,
SLAVE_TAG,
)
pattern_dict_slave = {
Stages.STACK_IPV4: (
r"I \([0-9]+\) example_connect: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
r"I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
),
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (r"I \(([0-9]+)\) SLAVE_TEST: (Modbus slave stack initialized)"),
Stages.STACK_INIT: (r"I \(([0-9]+)\) [A-Z_]*: (Modbus slave stack initialized)."),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) SLAVE_TEST: Socket \(#[0-9]+\), accept client connection from address: "
r"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
r"I\s\(([0-9]+)\) port.utils: Socket \(#[0-9]+\), accept client connection from address\[port\]: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\[[0-9]+\]"
),
Stages.STACK_START: (r"I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)"),
Stages.STACK_START: (r"I\s\(([0-9]+)\) [A-Z_]+: Slave TCP [#0-9]*\s*is started"),
Stages.STACK_PAR_OK: (
r"I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s"
r"ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+"
r"I\s\(([0-9]+)\) [A-Z_]+: OBJ (0x[a-fA-Z0-9]+),() ([A-Za-z\s]+) \([0-9]+ us\),\s*[A-Z:]*\s*[0-9,]*\s*[A-Z:]*[0-9]*, TYPE:[0-9]+, INST_ADDR:0x[a-fA-Z0-9]+[()0-9a-z]+, SIZE:[0-9]+"
),
Stages.STACK_PAR_FAIL: (
r"E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet"
),
Stages.STACK_DESTROY: (
r"I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed)."
Stages.STACK_DESTROY: (r"I\s\(([0-9]+)\) [A-Z_]+: Destroy slave"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_[a-z]+[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.slave: mbc_[a-z]+_slave_get_parameter: Good response for get cid\(([0-9]+)\) = ESP_OK"
),
}
pattern_dict_master = {
Stages.STACK_IPV4: (
r"I \([0-9]+\) example_connect: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
r"I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
),
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (
r"I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized)"
r"I \(([0-9]+)\) [A-Z_]*: [0xa-f0-9,]*\s*Modbus master stack initialized"
),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling"
r"I\s\(([0-9]+)\) mb_port.tcp.master: 0x[a-f0-9]+, Connected: [0-9], [0-9], start polling."
),
Stages.STACK_START: (r"I \(([0-9]+)\) MASTER_TEST: (Start modbus test)"),
Stages.STACK_START: (r"I \(([0-9]+)\) [A-Z_]+: Start modbus test..."),
Stages.STACK_PAR_OK: (
r"I \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)"
r"\s\([a-zA-Z\_\%\/]+\) value =[a-zA-Z0-9\.\s]* \((0x[a-zA-Z0-9]+)\)[,\sa-z]+ successful"
r"I \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) ([a-zA-Z0-9\_]+) \([\%a-zA-Z_\/]+\) value = ([0-9a-zA-Z.]*)\s*([a-zA-Z0-9()]*) read successful"
),
Stages.STACK_PAR_FAIL: (
r".*E \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+\s\(([a-zA-Z0-9_]+)\)\s"
r"read fail, err = [0-9]+ \([_A-Z]+\)"
r"E \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) \(([a-zA-Z0-9_]+)\) read fail, err = [x0-9]+ \([_A-Z]+\)"
),
Stages.STACK_DESTROY: (r"I \(([0-9]+)\) [A-Z_]+: (Master Serial is completed)"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_[a-z]+[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.master: mbc_[a-z]+_master_get_parameter: ([a-zA-Z0-9]+) Good response for get cid\(([0-9]+)\) = ESP_OK"
),
Stages.STACK_DESTROY: (r"I \(([0-9]+)\) MASTER_TEST: (Destroy master)..."),
}
LOG_LEVEL = logging.DEBUG
@@ -94,9 +111,70 @@ def test_modbus_serial_communication(
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
### Slave and Master objects registered
slave_objects = dut_slave.get_objects_by_tag(SLAVE_TAG)
for object in slave_objects:
logger.info("Modbus slave objects: %s", object)
logger.info("Number of slave objects: %s", len(slave_objects))
master_objects = dut_master.get_objects_by_tag(MASTER_TAG)
for object in master_objects:
logger.info("Modbus master objects: %s", object)
logger.info("Number of master objects: %s", len(master_objects))
params_timestamp = dut_master.get_params_by_timestamp_range(40000, 45000)
if params_timestamp:
for param in params_timestamp:
logger.info("Modbus params by timestamp: %s", param)
### Slave Fail and Success Params
slave_success_params = dut_slave.get_slave_params_by_status(PARAM_SUCCESS)
logger.info("Total successful slave parameters: %d", len(slave_success_params))
slave_fail_params = dut_slave.get_slave_params_by_status(PARAM_FAIL)
logger.info("Total fail slave parameters: %d", len(slave_fail_params))
all_slave_params = len(slave_success_params) + len(slave_fail_params)
if all_slave_params:
logger.info(
"All slave parameters: %s, Ratio of successful slave parameters: %s",
all_slave_params,
(len(slave_success_params) / all_slave_params) * 100,
)
### Master Fail and Success Params
master_success_params = dut_master.get_master_params_by_status(PARAM_SUCCESS)
logger.info("Total successful master parameters: %d", len(master_success_params))
master_fail_params = dut_master.get_master_params_by_status(PARAM_FAIL)
logger.info("Total fail master parameters: %d", len(master_fail_params))
all_master_params = len(master_success_params) + len(master_fail_params)
if all_master_params:
logger.info(
"All master parameters: %s, Ratio of successful master parameters: %s",
all_master_params,
(len(master_success_params) / all_master_params) * 100,
)
### Master average response time for successful requests
logger.info(
"Average response time for successful master requests: %d ms",
dut_master.get_avg_response_time_master(),
)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()
### Histogram Modbus stats
dut_master.plot_modbus_stats(
master_objects,
dut_master.dut_stats_info(),
slave_objects,
dut_slave.dut_stats_info(),
"modbus_stats_graph",
)
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_generic
+86 -70
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -26,6 +26,7 @@
#define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example
// The number of parameters that intended to be used in the particular control process
#define MASTER_MAX_CIDS num_device_parameters
@@ -91,9 +92,9 @@ static const char *TAG = "MASTER_TEST";
// Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table
enum {
MB_DEVICE_ADDR1 = 1, // Slave UID = 1
MB_DEVICE_ADDR2,
MB_DEVICE_ADDR3,
MB_DEVICE_COUNT = 3
//MB_DEVICE_ADDR1,
//MB_DEVICE_ADDR3,
MB_DEVICE_COUNT = 2
};
// Enumeration of all supported CIDs for device (used in parameter definition table)
@@ -160,19 +161,19 @@ const mb_parameter_descriptor_t device_parameters[] = {
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING,
CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data1), TEST_HOLD_REG_SIZE(holding_data1),
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_INPUT,
CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data2), TEST_INPUT_REG_SIZE(input_data2),
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR3, MB_PARAM_HOLDING,
CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data2), TEST_HOLD_REG_SIZE(holding_data2),
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
@@ -316,14 +317,14 @@ const size_t ip_table_sz;
char *slave_ip_address_table[MB_DEVICE_COUNT + 1] = {
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR2 and set to predefined value by user
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR3 and set to predefined value by user
//"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR2 and set to predefined value by user
//"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR3 and set to predefined value by user
NULL // End of table condition (must be included)
#elif CONFIG_MB_MDNS_IP_RESOLVER
// This is workaround for the test to use the same slave for all CIDs and ignore UID setting in the slave
"01;mb_slave_tcp_01;1502",
"02;mb_slave_tcp_01;1502",
"03;mb_slave_tcp_01;1502",
// "02;mb_slave_tcp_01;502",
//"03;mb_slave_tcp_01;1502",
NULL // End of table condition (must be included)
#endif
};
@@ -452,54 +453,59 @@ static void *master_get_param_data(const mb_parameter_descriptor_t *param_descri
return instance_ptr;
}
#define TEST_VERIFY_VALUES(handle, descr, inst) (__extension__( \
{ \
assert(inst); \
assert(descr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, descr->cid, (uint8_t *)inst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (descr->param_opts.opt3) { \
for EACH_ITEM(inst, descr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(inst)))descr->param_opts.opt3) { \
*item_ptr = (typeof(*(inst)))descr->param_opts.opt3; \
ESP_LOGD(TAG, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
(int)descr->cid, \
(char *)descr->param_key, \
(uint16_t)descr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
(int)descr->cid, \
(char *)descr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)inst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
(int)descr->cid, \
(char *)descr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
(int)descr->cid, \
(char *)descr->param_key, \
(char *)descr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
(int)descr->cid, \
(char *)descr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
#define TEST_VERIFY_VALUES(handle, pdescr, pinst) (__extension__( \
{ \
assert(pinst); \
assert(pdescr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, pdescr->cid, (uint8_t *)pinst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*item_ptr = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "%p Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s), initialize.", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)pinst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (..) write successful.", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(char *)pdescr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
))
// User operation function to read slave values and check alarm
@@ -509,7 +515,7 @@ static void master_operation_func(void *arg)
bool alarm_state = false;
const mb_parameter_descriptor_t *param_descriptor = NULL;
ESP_LOGI(TAG, "Start modbus test...");
ESP_LOGI(TAG, "Master TCP is started.");
char *pcustom_string = "Master";
mb_param_request_t req = {
@@ -539,7 +545,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_TEST_REG)) {
// Check test parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -550,7 +557,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_U16_BA)) {
// Check the uint16 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -560,7 +568,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_U8_B)) {
// Check the uint8 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -570,7 +579,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_UINT32_DCBA)) {
// Check the uint32 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -581,7 +591,8 @@ static void master_operation_func(void *arg)
&& (param_descriptor->cid <= CID_HOLD_FLOAT_DCBA)) {
// Check the float parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -591,7 +602,8 @@ static void master_operation_func(void *arg)
} else if (param_descriptor->cid >= CID_HOLD_DOUBLE_ABCDEFGH) {
// Check the double parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (double *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -601,7 +613,8 @@ static void master_operation_func(void *arg)
#endif
} else if (cid <= CID_HOLD_DATA_2) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -619,14 +632,16 @@ static void master_operation_func(void *arg)
uint8_t state = *(uint8_t *)temp_data_ptr;
const char *rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
} else {
ESP_LOGE(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
ESP_LOGE(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
master_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
@@ -798,7 +813,7 @@ static esp_err_t master_destroy(void)
TAG,
"mbc_master_destroy fail, returns(0x%x).",
(int)err);
ESP_LOGI(TAG, "Modbus master stack destroy...");
ESP_LOGI(TAG, "Destroy master stack");
return err;
}
@@ -828,4 +843,5 @@ void app_main(void)
master_operation_func(NULL);
ESP_ERROR_CHECK(master_destroy());
ESP_ERROR_CHECK(destroy_services());
ESP_LOGI(TAG, "Master TCP is completed. (%s).", __func__);
}
@@ -10,10 +10,10 @@ CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed
CONFIG_MB_SLAVE_IP_FROM_STDIN=n
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
+3 -3
View File
@@ -7,9 +7,9 @@ CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_MB_SLAVE_IP_FROM_STDIN=n
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
@@ -11,8 +11,8 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_MB_SLAVE_IP_FROM_STDIN=n
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
+33 -20
View File
@@ -1,9 +1,13 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// esp-modbus Slave Example ESP32
#include <stdio.h>
#include "esp_err.h"
#include "sdkconfig.h"
@@ -55,6 +59,7 @@
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
#define MB_CUST_DATA_MAX_LEN (100)
static const char *TAG = "SLAVE_TEST";
static void *slave_handle = NULL;
@@ -123,33 +128,35 @@ static void setup_reg_data(void)
coil_reg_params.coils_port0 = 0x55;
coil_reg_params.coils_port1 = 0xAA;
input_reg_params.input_data0 = 1.12F;
input_reg_params.input_data1 = 2.34F;
input_reg_params.input_data2 = 3.56F;
input_reg_params.input_data3 = 4.78F;
input_reg_params.input_data4 = 1.12F;
input_reg_params.input_data5 = 2.34F;
input_reg_params.input_data6 = 3.56F;
input_reg_params.input_data7 = 4.78F;
input_reg_params.input_data0 = 1.12;
input_reg_params.input_data1 = 2.34;
input_reg_params.input_data2 = 3.56;
input_reg_params.input_data3 = 4.78;
input_reg_params.input_data4 = 6.12;
input_reg_params.input_data5 = 7.34;
input_reg_params.input_data6 = 8.56;
input_reg_params.input_data7 = 9.78;
}
static void slave_operation_func(void *arg)
{
mb_param_info_t reg_info; // keeps the Modbus registers access information
ESP_LOGI(TAG, "Modbus slave stack initialized.");
ESP_LOGI(TAG, "Slave TCP is started");
ESP_LOGI(TAG, "Start modbus test...");
// The cycle below will be terminated when parameter holding_data0
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
for (; holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
// Check for read/write events of Modbus master for certain events
(void)mbc_slave_check_event(slave_handle, MB_READ_WRITE_MASK);
ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT));
const char *rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
(void)mbc_slave_check_event(slave_handle, MB_READ_WRITE_MASK); // checks every type of event from specific slave , parameter queue
ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT)); // get latest info from parameter queue
const char *rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE"; //only checks read mask, assumes write if not
// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
// Get parameter information from parameter queue
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
slave_handle,
rw_str,
(unsigned)reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
@@ -166,21 +173,24 @@ static void slave_operation_func(void *arg)
(void)mbc_slave_unlock(slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "DISCRETE READ (%" PRIu32 " us): ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, DISCRETE READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
ESP_LOGI(TAG, "OBJ %p, COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
@@ -194,7 +204,7 @@ static void slave_operation_func(void *arg)
}
}
// Destroy of Modbus controller on alarm
ESP_LOGI(TAG, "Modbus controller destroyed.");
ESP_LOGI(TAG, "Destroy slave.");
vTaskDelay(100);
}
@@ -428,7 +438,7 @@ void app_main(void)
// Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
mb_communication_info_t tcp_slave_config = {
mb_communication_info_t tcp_slave_config_1 = {
.tcp_opts.port = MB_TCP_PORT_NUMBER,
.tcp_opts.mode = MB_TCP,
#if !CONFIG_EXAMPLE_CONNECT_IPV6
@@ -441,7 +451,10 @@ void app_main(void)
.tcp_opts.uid = MB_SLAVE_ADDR
};
ESP_ERROR_CHECK(slave_init(&tcp_slave_config));
ESP_ERROR_CHECK(slave_init(&tcp_slave_config_1));
ESP_LOGI(TAG, "Slave TCP #1 is started (%s)", __func__);
// The Modbus slave logic is located in this function (user handling of Modbus)
slave_operation_func(NULL);
@@ -11,7 +11,7 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed!
+2 -2
View File
@@ -8,8 +8,8 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed!
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
+1 -1
View File
@@ -10,7 +10,7 @@ CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
+14 -11
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
@@ -34,23 +34,26 @@ pattern_dict_slave = {
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (
r"I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized)."
),
Stages.STACK_INIT: (r"I \(([0-9]+)\) [A-Z_]*: (Modbus slave stack initialized)."),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: "
r"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
r"I\s\(([0-9]+)\) port.utils: Socket \(#[0-9]+\), accept client connection from address\[port\]: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\[[0-9]+\]"
),
Stages.STACK_START: (r"I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)"),
Stages.STACK_START: (r"I\s\(([0-9]+)\) [A-Z_]+: Slave TCP [#0-9]*\s*is started"),
Stages.STACK_PAR_OK: (
r"I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s"
r"ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+"
r"I\s\(([0-9]+)\) [A-Z_]+: OBJ (0x[a-fA-Z0-9]+),() ([A-Za-z\s]+) \([0-9]+ us\),\s*[A-Z:]*\s*[0-9,]*\s*[A-Z:]*[0-9]*, TYPE:[0-9]+, INST_ADDR:0x[a-fA-Z0-9]+[()0-9a-z]+, SIZE:[0-9]+"
),
Stages.STACK_PAR_FAIL: (
r"E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet"
),
Stages.STACK_DESTROY: (
r"I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed)."
Stages.STACK_DESTROY: (r"I\s\(([0-9]+)\) [A-Z_]+: Destroy slave"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_tcp[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.slave: mbc_[a-z]+_slave_get_parameter: Good response for get cid\(([0-9]+)\) = ESP_OK"
),
}
+111 -26
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
@@ -7,10 +7,15 @@
import logging
import os
from typing import Tuple
import pytest
from conftest import ModbusTestDut, Stages
from conftest import (
ModbusTestDut,
Stages,
PARAM_SUCCESS,
PARAM_FAIL,
MASTER_TAG,
SLAVE_TAG,
)
pattern_dict_slave = {
Stages.STACK_IPV4: (
@@ -19,21 +24,26 @@ pattern_dict_slave = {
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (r"I \(([0-9]+)\) SLAVE_TEST: (Modbus slave stack initialized)"),
Stages.STACK_INIT: (r"I \(([0-9]+)\) [A-Z_]*: (Modbus slave stack initialized)."),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: "
r"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
r"I\s\(([0-9]+)\) port.utils: Socket \(#[0-9]+\), accept client connection from address\[port\]: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\[[0-9]+\]"
),
Stages.STACK_START: (r"I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)"),
Stages.STACK_START: (r"I\s\(([0-9]+)\) [A-Z_]+: Slave TCP [#0-9]*\s*is started"),
Stages.STACK_PAR_OK: (
r"I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s"
r"ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+"
r"I\s\(([0-9]+)\) [A-Z_]+: OBJ (0x[a-fA-Z0-9]+),() ([A-Za-z\s]+) \([0-9]+ us\),\s*[A-Z:]*\s*[0-9,]*\s*[A-Z:]*[0-9]*, TYPE:[0-9]+, INST_ADDR:0x[a-fA-Z0-9]+[()0-9a-z]+, SIZE:[0-9]+"
),
Stages.STACK_PAR_FAIL: (
r"E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet"
),
Stages.STACK_DESTROY: (
r"I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed)."
Stages.STACK_DESTROY: (r"I\s\(([0-9]+)\) [A-Z_]+: Destroy slave"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_tcp[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.slave: mbc_[a-z]+_slave_get_parameter: Good response for get cid\(([0-9]+)\) = ESP_OK"
),
}
@@ -45,28 +55,41 @@ pattern_dict_master = {
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (
r"I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized)"
r"I \(([0-9]+)\) [A-Z_]*: [0xa-f0-9,]*\s*Modbus master stack initialized"
),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling"
r"I\s\(([0-9]+)\) mb_port.tcp.master: 0x[a-f0-9]+, Connected: [0-9], [0-9], start polling."
),
Stages.STACK_START: (r"I \(([0-9]+)\) MASTER_TEST: (Start modbus test)"),
Stages.STACK_START: (r"I \(([0-9]+)\) [A-Z_]+: Master TCP is started"),
Stages.STACK_PAR_OK: (
r"I \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)"
r"\s\([a-zA-Z\_\%\/]+\) value =[a-zA-Z0-9\.\s]* \((0x[a-zA-Z0-9]+)\)[,\sa-z]+ successful"
r"I \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) ([a-zA-Z0-9\_]+) \([\%a-zA-Z_\/]+\) value = ([0-9a-zA-Z.]*)\s*([a-zA-Z0-9()]*) read successful"
),
Stages.STACK_PAR_FAIL: (
r".*E \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+\s\(([a-zA-Z0-9_]+)\)\s"
r"read fail, err = [x0-9]+ \([_A-Z]+\)"
r"E \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) \(([a-zA-Z0-9_]+)\) read fail, err = [x0-9]+ \([_A-Z]+\)"
),
Stages.STACK_DESTROY: (r"I \(([0-9]+)\) [A-Z_]+: (Master TCP is completed.)"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_tcp[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.master: mbc_[a-z]+_master_get_parameter: ([a-zA-Z0-9]+) Good response for get cid\(([0-9]+)\) = ESP_OK"
),
Stages.STACK_DESTROY: (r"I \(([0-9]+)\) MASTER_TEST: (Destroy master)..."),
}
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = "modbus_test"
CONFORMANCE_TEST_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../tools/robot")
)
logger = logging.getLogger(LOGGER_NAME)
test_configs = ["ethernet"]
test_configs = [
# 'wifi',
"ethernet"
]
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@@ -86,21 +109,83 @@ def test_modbus_tcp_communication(dut: Tuple[ModbusTestDut, ModbusTestDut]) -> N
dut_slave = dut[0]
dut_master = dut[1]
logger.info("DUT: %s start.", dut_slave.dut_get_name())
logger.info("DUT: %s start.", dut_master.dut_get_name())
dut_slave_ip_address = dut_slave.dut_get_ip()
dut_master.dut_send_ip(dut_slave_ip_address)
logger.info("DUT: %s start.", dut_slave.dut_get_name())
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
### Slave and Master objects registered
slave_objects = dut_slave.get_objects_by_tag(SLAVE_TAG)
for object in slave_objects:
logger.info("Modbus slave objects: %s", object)
logger.info("Number of slave objects: %s", len(slave_objects))
master_objects = dut_master.get_objects_by_tag(MASTER_TAG)
for object in master_objects:
logger.info("Modbus master objects: %s", object)
logger.info("Number of master objects: %s", len(master_objects))
params_timestamp = dut_master.get_params_by_timestamp_range(40000, 45000)
if params_timestamp:
for param in params_timestamp:
logger.info("Modbus params by timestamp: %s", param)
### Slave Fail and Success Params
slave_success_params = dut_slave.get_slave_params_by_status(PARAM_SUCCESS)
logger.info("Total successful slave parameters: %d", len(slave_success_params))
slave_fail_params = dut_slave.get_slave_params_by_status(PARAM_FAIL)
logger.info("Total fail slave parameters: %d", len(slave_fail_params))
all_slave_params = len(slave_success_params) + len(slave_fail_params)
if all_slave_params:
logger.info(
"All slave parameters: %s, Ratio of successful slave parameters: %s",
all_slave_params,
(len(slave_success_params) / all_slave_params) * 100,
)
### Master Fail and Success Params
master_success_params = dut_master.get_master_params_by_status(PARAM_SUCCESS)
logger.info("Total successful master parameters: %d", len(master_success_params))
master_fail_params = dut_master.get_master_params_by_status(PARAM_FAIL)
logger.info("Total fail master parameters: %d", len(master_fail_params))
all_master_params = len(master_success_params) + len(master_fail_params)
if all_master_params:
logger.info(
"All master parameters: %s, Ratio of successful master parameters: %s",
all_master_params,
(len(master_success_params) / all_master_params) * 100,
)
### Master average response time for successful requests
logger.info(
"Average response time for successful master requests: %d ms",
dut_master.get_avg_response_time_master(),
)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()
### Histogram Modbus stats
dut_master.plot_modbus_stats(
master_objects,
dut_master.dut_stats_info(),
slave_objects,
dut_slave.dut_stats_info(),
"modbus_stats_graph",
)
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_generic
@pytest.mark.parametrize("config", ["dummy_config"])
def test_modbus_tcp_generic(config: str) -> None:
def test_modbus_tcp_generic(config) -> None:
logger.info("The generic tcp example tests are not provided yet.")
if __name__ == "__main__":
pytest.main(["pytest_mb_tcp_instances.py"])
@@ -181,10 +181,12 @@ esp_err_t mbc_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbm_controller->set_descriptor,
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->set_descriptor(ctx, descriptor, num_elements);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master set descriptor failure, error=(0x%x) (%s).",
@@ -319,6 +319,7 @@ static esp_err_t mbc_serial_master_get_parameter(void *ctx, uint16_t cid, uint8_
{
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
@@ -339,23 +340,23 @@ static esp_err_t mbc_serial_master_get_parameter(void *ctx, uint16_t cid, uint8_
error = mbc_master_set_param_data((void *)value, (void *)data_ptr,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
ESP_LOGE(TAG, "%p fail to set parameter data.", mbm_controller_iface);
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
ESP_LOGD(TAG, "%s: %p Good response for get cid(%u) = %s",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
} else {
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
ESP_LOGD(TAG, "%s: %p Bad response to get cid(%u) = %s",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(data_ptr);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
ESP_LOGE(TAG, "%s: %p The cid(%u) not found in the data dictionary.",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
+9 -9
View File
@@ -331,8 +331,8 @@ static esp_err_t mbc_tcp_master_get_parameter(void *ctx, uint16_t cid, uint8_t *
mb_uid_info_t *addr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
request.slave_addr, MB_SOCK_STATE_CONNECTED);
if (!addr_info) {
ESP_LOGW(TAG, "Try to send request for cid #%u with uid = %d, node is disconnected.",
(unsigned)reg_info.cid, (int)request.slave_addr);
ESP_LOGW(TAG, "%p Try to send request for cid #%u with uid = %d, node is disconnected.",
mbm_controller_iface, (unsigned)reg_info.cid, (int)request.slave_addr);
}
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
// alloc buffer to store parameter data
@@ -347,23 +347,23 @@ static esp_err_t mbc_tcp_master_get_parameter(void *ctx, uint16_t cid, uint8_t *
error = mbc_master_set_param_data((void *)value, (void *)data_ptr,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
ESP_LOGE(TAG, "%p fail to set parameter data.", mbm_controller_iface);
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
ESP_LOGD(TAG, "%s: %p Good response for get cid(%u) = %s",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
} else {
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
ESP_LOGD(TAG, "%s: %p Bad response to get cid(%u) = %s",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(data_ptr);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
ESP_LOGE(TAG, "%s: %p The cid(%u) not found in the data dictionary.",
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
+14
View File
@@ -5,6 +5,20 @@ physical_tests:
disable:
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1 and IDF_TARGET != "esp32"
tcp_instances_tests/mb_tcp_master_instances:
disable_test:
- if: IDF_TARGET != "esp32"
reason: only manual test is performed for other targets
disable:
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1 and IDF_TARGET != "esp32"
tcp_instances_tests/mb_tcp_slave_instances:
disable_test:
- if: IDF_TARGET != "esp32"
reason: only manual test is performed for other targets
disable:
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1 and IDF_TARGET != "esp32"
unit_tests:
disable_test:
- if: IDF_TARGET != "esp32"
@@ -73,7 +73,7 @@ TEST_SETUP(modbus_adapter_serial)
TEST_TEAR_DOWN(modbus_adapter_serial)
{
int task_count = test_common_task_start_all(1);
int task_count = test_common_task_start_all();
TEST_ASSERT_TRUE(task_count > 0);
TEST_ASSERT_EQUAL(task_count, test_common_task_wait_done_delete_all(TEST_TASK_TIMEOUT_MS));
test_common_stop();
@@ -72,7 +72,7 @@ TEST_SETUP(modbus_adapter_tcp)
TEST_TEAR_DOWN(modbus_adapter_tcp)
{
int task_count = test_common_task_start_all(1);
int task_count = test_common_task_start_all();
TEST_ASSERT_TRUE(task_count > 0);
TEST_ASSERT_EQUAL(task_count, test_common_task_wait_done_delete_all(TEST_TASK_TIMEOUT_MS));
test_common_stop();
@@ -196,7 +196,7 @@ static void test_modbus_tcp_slave(void)
unity_send_signal("Slave_ready");
unity_wait_for_signal("Master_started");
TEST_ASSERT_EQUAL(test_common_task_start_all(1),
TEST_ASSERT_EQUAL(test_common_task_start_all(),
test_common_task_wait_done_delete_all(TEST_TCP_TASK_TIMEOUT_MS));
ESP_LOGI(TAG, "Slave TCP test is completed. (%s).", __func__);
@@ -235,7 +235,7 @@ static void test_modbus_tcp_master(void)
unity_send_signal("Master_started");
TEST_ASSERT_EQUAL(test_common_task_start_all(1),
TEST_ASSERT_EQUAL(test_common_task_start_all(),
test_common_task_wait_done_delete_all(TEST_TCP_TASK_TIMEOUT_MS));
test_common_stop();
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
@@ -0,0 +1,17 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
# Exclude old component feemodbus which exists in old versions
set(EXCLUDE_COMPONENTS freemodbus)
set(EXTRA_COMPONENT_DIRS "../../test_common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# The workaround for the test_utils under ESP-IDF v6.0
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.5")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
else()
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
endif()
project(modbus_tcp_master_instances)
@@ -0,0 +1,4 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
This test app is a application to test multi-instance modbus communication.
@@ -0,0 +1,6 @@
set(PROJECT_NAME "modbus_tcp_master_instances")
idf_component_register(SRCS "tcp_master_instances.c"
INCLUDE_DIRS "."
PRIV_REQUIRES test_utils test_common unity nvs_flash esp_event esp_eth
)
@@ -0,0 +1,45 @@
menu "Modbus Test Configuration"
config MB_PORT_ADAPTER_EN
bool "Enable Modbus port adapter to substitute hardware layer for test."
default y
help
When option is enabled the port communication layer is substituted by
port adapter layer to allow testing of higher layers without access to physical layer.
config MB_TEST_SLAVE_TASK_PRIO
int "Modbus master test task priority"
range 4 23
default 4
help
Modbus master task priority for the test.
config MB_TEST_MASTER_TASK_PRIO
int "Modbus slave test task priority"
range 4 23
default 4
help
Modbus slave task priority for the test.
config MB_TEST_COMM_CYCLE_COUNTER
int "Modbus test communication cycle counter"
range 10 1000
default 10
help
Modbus communication cycle counter for test.
config MB_TEST_LEAK_WARN_LEVEL
int "Modbus test leak warning level"
range 4 256
default 32
help
Modbus test leak warning level.
config MB_TEST_LEAK_CRITICAL_LEVEL
int "Modbus test leak critical level"
range 4 1024
default 64
help
Modbus test leak critical level.
endmenu
@@ -0,0 +1,12 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
espressif/mdns:
version: "^1.0.0"
mb_example_common:
path: "../../../../tools/mb_example_common"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
@@ -0,0 +1,699 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "test_common.h"
#include "test_utils.h"
#include "esp_err.h"
#include "mbc_master.h"
#include "mbc_slave.h"
#include "nvs_flash.h"
#if MB_MDNS_IS_INCLUDED
#include "mdns.h"
#endif
#include "mbcontroller.h" // for mbcontroller defines and api
#include "modbus_params.h" // for modbus parameters structures
#include "protocol_examples_common.h"
#include "esp_event.h"
#if __has_include("unity_test_utils.h")
// unity test utils are used
#include "unity_test_utils.h"
#else
// Unit_test_app utils from test_utils ("test_utils.h"), v4.4
#define unity_utils_task_delete test_utils_task_delete
#endif
#define TEST_TCP_PORT_NUM1 (1502)
#define TEST_TCP_TASK_TIMEOUT_MS (160000)
#define TEST_TCP_MASTER_SEND_TOUT_US (500)
#define TEST_TASK_START_TIMEOUT (10000 / portTICK_PERIOD_MS)
#define TEST_TASK_TICK_TIME (50 / portTICK_PERIOD_MS)
#define TEST_TASK_NOTIFY_STOP_TOUT (200 / portTICK_PERIOD_MS)
#define TEST_VALUE (12345) // default test value
#define TEST_ASCII_BIN (0xAAAAAAAA)
#define TEST_ARR_REG_SZ (58)
#define TEST_HUMI_MIN (-40)
#define TEST_HUMI_MAX (50)
#define TEST_TEMP_MIN (0)
#define TEST_TEMP_MAX (100)
// Number of reading of parameters from slave
#define MASTER_MAX_RETRY (10)
// The number of parameters that intended to be used in the particular control process
#define MASTER_MAX_CIDS num_descriptors
// The macro to get offset for parameter in the appropriate structure
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
#define TEST_HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
#define TEST_HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
#define TEST_INPUT_REG_START(field) (INPUT_OFFSET(field) >> 1)
#define TEST_INPUT_REG_SIZE(field) (sizeof(((input_reg_params_t *)0)->field) >> 1)
// Options can be used as bit masks or parameter limits
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
#define EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
#define TEST_MASTER_RESPOND_TOUT_MS (CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND)
// The workaround to statically link the whole test library
__attribute__((unused)) bool mb_test_include_phys_impl_tcp = true;
#define TAG "MODBUS_TCP_COMM_MASTER_TEST"
// Enumeration of all supported CIDs for device (used in parameter definition table)
enum {
CID_INP_DATA_0 = 0,
CID_HOLD_DATA_0,
CID_INP_DATA_1,
CID_HOLD_DATA_1,
CID_INP_DATA_2,
CID_HOLD_DATA_2,
CID_HOLD_TEST_REG,
CID_RELAY_P1,
CID_RELAY_P2,
CID_DISCR_P1,
#if CONFIG_FMB_EXT_TYPE_SUPPORT
CID_HOLD_U8_A,
CID_HOLD_U8_B,
CID_HOLD_U16_AB,
CID_HOLD_U16_BA,
CID_HOLD_UINT32_ABCD,
CID_HOLD_UINT32_CDAB,
CID_HOLD_UINT32_BADC,
CID_HOLD_UINT32_DCBA,
CID_HOLD_FLOAT_ABCD,
CID_HOLD_FLOAT_CDAB,
CID_HOLD_FLOAT_BADC,
CID_HOLD_FLOAT_DCBA,
CID_HOLD_DOUBLE_ABCDEFGH,
CID_HOLD_DOUBLE_HGFEDCBA,
CID_HOLD_DOUBLE_GHEFCDAB,
CID_HOLD_DOUBLE_BADCFEHG,
#endif
CID_COUNT
};
// Example Data (Object) Dictionary for Modbus parameters
const mb_parameter_descriptor_t descriptors[] = {
{
CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data0), TEST_INPUT_REG_SIZE(input_data0),
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data0), TEST_HOLD_REG_SIZE(holding_data0),
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data1), TEST_INPUT_REG_SIZE(input_data1),
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data1), TEST_HOLD_REG_SIZE(holding_data1),
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data2), TEST_INPUT_REG_SIZE(input_data2),
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data2), TEST_HOLD_REG_SIZE(holding_data2),
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(test_regs), TEST_ARR_REG_SZ,
HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, (TEST_ARR_REG_SZ * 2),
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, TEST_ASCII_BIN ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 2, 6,
COIL_OFFSET(coils_port0), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 10, 6,
COIL_OFFSET(coils_port1), PARAM_TYPE_U8, 1,
OPTS( 0x55, 0x2A, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_DISCR_P1, STR("DiscreteInpP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 2, 7,
DISCR_OFFSET(discrete_input_port1), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
#if CONFIG_FMB_EXT_TYPE_SUPPORT
{
CID_HOLD_U8_A, STR("U8_A"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_a), TEST_HOLD_REG_SIZE(holding_u8_a),
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (TEST_HOLD_REG_SIZE(holding_u8_a) << 1),
OPTS( CHAR_MIN, 0x0055, 0x0055 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_U8_B, STR("U8_B"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_b), TEST_HOLD_REG_SIZE(holding_u8_b),
HOLD_OFFSET(holding_u8_b), PARAM_TYPE_U8_B, (TEST_HOLD_REG_SIZE(holding_u8_b) << 1),
OPTS( 0, 0x5500, 0x5500 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_U16_AB, STR("U16_AB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ab), TEST_HOLD_REG_SIZE(holding_u16_ab),
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_U16_BA, STR("U16_BA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ba), TEST_HOLD_REG_SIZE(holding_u16_ba),
HOLD_OFFSET(holding_u16_ba), PARAM_TYPE_U16_BA, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_abcd), TEST_HOLD_REG_SIZE(holding_uint32_abcd),
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (TEST_HOLD_REG_SIZE(holding_uint32_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_UINT32_CDAB, STR("UINT32_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_cdab), TEST_HOLD_REG_SIZE(holding_uint32_cdab),
HOLD_OFFSET(holding_uint32_cdab), PARAM_TYPE_U32_CDAB, (TEST_HOLD_REG_SIZE(holding_uint32_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_UINT32_BADC, STR("UINT32_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_badc), TEST_HOLD_REG_SIZE(holding_uint32_badc),
HOLD_OFFSET(holding_uint32_badc), PARAM_TYPE_U32_BADC, (TEST_HOLD_REG_SIZE(holding_uint32_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_UINT32_DCBA, STR("UINT32_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_dcba), TEST_HOLD_REG_SIZE(holding_uint32_dcba),
HOLD_OFFSET(holding_uint32_dcba), PARAM_TYPE_U32_DCBA, (TEST_HOLD_REG_SIZE(holding_uint32_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_FLOAT_ABCD, STR("FLOAT_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_abcd), TEST_HOLD_REG_SIZE(holding_float_abcd),
HOLD_OFFSET(holding_float_abcd), PARAM_TYPE_FLOAT_ABCD, (TEST_HOLD_REG_SIZE(holding_float_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_cdab), TEST_HOLD_REG_SIZE(holding_float_cdab),
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (TEST_HOLD_REG_SIZE(holding_float_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_FLOAT_BADC, STR("FLOAT_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_badc), TEST_HOLD_REG_SIZE(holding_float_badc),
HOLD_OFFSET(holding_float_badc), PARAM_TYPE_FLOAT_BADC, (TEST_HOLD_REG_SIZE(holding_float_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_FLOAT_DCBA, STR("FLOAT_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_dcba), TEST_HOLD_REG_SIZE(holding_float_dcba),
HOLD_OFFSET(holding_float_dcba), PARAM_TYPE_FLOAT_DCBA, (TEST_HOLD_REG_SIZE(holding_float_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DOUBLE_ABCDEFGH, STR("DOUBLE_ABCDEFGH"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_abcdefgh), TEST_HOLD_REG_SIZE(holding_double_abcdefgh),
HOLD_OFFSET(holding_double_abcdefgh), PARAM_TYPE_DOUBLE_ABCDEFGH, (TEST_HOLD_REG_SIZE(holding_double_abcdefgh) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_hgfedcba), TEST_HOLD_REG_SIZE(holding_double_hgfedcba),
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (TEST_HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DOUBLE_GHEFCDAB, STR("DOUBLE_GHEFCDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_ghefcdab), TEST_HOLD_REG_SIZE(holding_double_ghefcdab),
HOLD_OFFSET(holding_double_ghefcdab), PARAM_TYPE_DOUBLE_GHEFCDAB, (TEST_HOLD_REG_SIZE(holding_double_ghefcdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DOUBLE_BADCFEHG, STR("DOUBLE_BADCFEHG"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_badcfehg), TEST_HOLD_REG_SIZE(holding_double_badcfehg),
HOLD_OFFSET(holding_double_badcfehg), PARAM_TYPE_DOUBLE_BADCFEHG, (TEST_HOLD_REG_SIZE(holding_double_badcfehg) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER
}
#endif
};
// The number of parameters in the table
const uint16_t num_descriptors = (sizeof(descriptors) / sizeof(descriptors[0]));
const char *slave_tcp_addr_table[] = {
"01;mb_slave_tcp_01;1502", // Corresponds to characteristic MB_DEVICE_ADDR1
"200;mb_slave_tcp_c8;502", // Corresponds to characteristic MB_DEVICE_ADDR2
NULL // End of table condition (must be included)
};
static esp_err_t test_tcp_services_init(void **pnetif)
{
esp_err_t result = nvs_flash_init();
if ((result == ESP_ERR_NVS_NO_FREE_PAGES) || (result == ESP_ERR_NVS_NEW_VERSION_FOUND)) {
ESP_ERROR_CHECK(nvs_flash_erase());
result = nvs_flash_init();
}
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_init fail, returns(0x%x).",
(int)result);
result = esp_netif_init();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_init fail, returns(0x%x).",
(int)result);
result = esp_event_loop_create_default();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_event_loop_create_default fail, returns(0x%x).",
(int)result);
#if MB_MDNS_IS_INCLUDED
// Start mdns service and register device
if (mdns_init() != ESP_OK) {
ESP_LOGE(TAG, "initialization of mdns fail.");
};
#endif
// This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
// Read "Establishing Wi-Fi or Ethernet Connection" section in
// examples/protocols/README.md for more information about this function.
result = example_connect();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"example_connect fail, returns(0x%x).",
(int)result);
#if CONFIG_EXAMPLE_CONNECT_WIFI
// result = esp_wifi_set_ps(WIFI_PS_NONE);
// ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
// TAG,
// "esp_wifi_set_ps fail, returns(0x%x).",
// (int)result);
#endif
if (pnetif) {
*pnetif = get_example_netif();
}
return ESP_OK;
}
static esp_err_t test_tcp_services_destroy(void)
{
esp_err_t err = ESP_OK;
err = example_disconnect();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"example_disconnect fail, returns(0x%x).",
(int)err);
err = esp_event_loop_delete_default();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_event_loop_delete_default fail, returns(0x%x).",
(int)err);
err = esp_netif_deinit();
ESP_RETURN_ON_FALSE(((err == ESP_OK) || (err == ESP_ERR_NOT_SUPPORTED)),
ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_deinit fail, returns(0x%x).",
(int)err);
err = nvs_flash_deinit();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_deinit fail, returns(0x%x).",
(int)err);
#if MB_MDNS_IS_INCLUDED
// Stop mdns service and register device
mdns_free();
#endif
return err;
}
// The function to get pointer to parameter storage (instance) according to parameter description table
void *master_get_param_data(const mb_parameter_descriptor_t *param_descriptor)
{
assert(param_descriptor != NULL);
void *instance_ptr = NULL;
if (param_descriptor->param_offset != 0) {
switch (param_descriptor->mb_param_type) {
case MB_PARAM_HOLDING:
instance_ptr = ((void *)&holding_reg_params + param_descriptor->param_offset - 1);
break;
case MB_PARAM_INPUT:
instance_ptr = ((void *)&input_reg_params + param_descriptor->param_offset - 1);
break;
case MB_PARAM_COIL:
instance_ptr = ((void *)&coil_reg_params + param_descriptor->param_offset - 1);
break;
case MB_PARAM_DISCRETE:
instance_ptr = ((void *)&discrete_reg_params + param_descriptor->param_offset - 1);
break;
default:
instance_ptr = NULL;
break;
}
} else {
ESP_LOGE(TAG, "Wrong parameter offset for CID #%u", param_descriptor->cid);
assert(instance_ptr != NULL);
}
return instance_ptr;
}
#define TEST_VERIFY_VALUES(handle, pdescr, pinst) (__extension__( \
{ \
assert(pinst); \
assert(pdescr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, pdescr->cid, (uint8_t *)pinst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*pitem)) { \
if (*pitem != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*pitem = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "%p Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s), initialize.", \
handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)pinst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (..) write successful.", \
handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(char *)pdescr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "%p Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
))
void func_task_master(void *arg)
{
void *mbm_handle = arg;
esp_err_t err = ESP_OK, request_err = ESP_OK;
bool message_destroy_found = false;
bool alarm_state = false;
const mb_parameter_descriptor_t *param_descriptor = NULL;
test_common_task_wait_start_and_stop(TEST_TASK_START_TIMEOUT);
ESP_LOGI(TAG, "Start modbus test...");
for (uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
// Read all found characteristics from slave(s)
for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) {
if (test_common_task_wait_start_and_stop(TEST_TASK_NOTIFY_STOP_TOUT)) {
ESP_LOGD(TAG, "Received destroy message, destroying instance: %p.", mbm_handle);
message_destroy_found = true;
break;
}
// Get data from parameters description table
// and use this information to fill the characteristics description table
// and having all required fields in just one table
esp_err_t err = mbc_master_get_cid_info(mbm_handle, cid, &param_descriptor);
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
void *temp_data_ptr = master_get_param_data(param_descriptor);
assert(temp_data_ptr);
if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
(param_descriptor->cid == CID_HOLD_TEST_REG)) {
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (uint32_t *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr);
}
#if CONFIG_FMB_EXT_TYPE_SUPPORT
} else if ((param_descriptor->cid >= CID_HOLD_U16_AB)
&& (param_descriptor->cid <= CID_HOLD_U16_BA)) {
// Check the uint16 parameters
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (uint16_t *)temp_data_ptr);
if (err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_U8_A)
&& (param_descriptor->cid <= CID_HOLD_U8_B)) {
// Check the uint8 parameters
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (uint16_t *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_UINT32_ABCD)
&& (param_descriptor->cid <= CID_HOLD_UINT32_DCBA)) {
// Check the uint32 parameters
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (uint32_t *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_FLOAT_ABCD)
&& (param_descriptor->cid <= CID_HOLD_FLOAT_DCBA)) {
// Check the float parameters
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (float *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if (param_descriptor->cid >= CID_HOLD_DOUBLE_ABCDEFGH) {
// Check the double parameters
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (double *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(double *)temp_data_ptr,
*(uint64_t *)temp_data_ptr);
}
#endif
} else if (cid <= CID_HOLD_DATA_2) {
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (float *)temp_data_ptr);
if (request_err == ESP_OK) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
float value = *(float *)temp_data_ptr;
if (((value > param_descriptor->param_opts.max) ||
(value < param_descriptor->param_opts.min))) {
alarm_state = true;
break;
}
} else if ((cid >= CID_RELAY_P1) && (cid <= CID_DISCR_P1)) {
request_err = TEST_VERIFY_VALUES(mbm_handle, param_descriptor, (uint8_t *)temp_data_ptr);
if (request_err == ESP_OK) {
uint8_t state = *(uint8_t *)temp_data_ptr;
const char *rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
ESP_LOGI(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
} else {
ESP_LOGE(TAG, "%p Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
mbm_handle,
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
alarm_state = true;
break;
}
if (state & param_descriptor->param_opts.opt1) {
alarm_state = true;
break;
}
}
}
if (request_err != ESP_OK) {
alarm_state = true;
break;
}
}
}
if (message_destroy_found) {
break;
}
vTaskDelay(TEST_TASK_TICK_TIME); // Let the IDLE task to trigger
if ((retry == MASTER_MAX_RETRY - 1) || alarm_state) {
ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
alarm_state = false;
break;
}
}
ESP_LOGI(TAG, "Destroy master, inst: %p.", mbm_handle);
TEST_ESP_OK(mbc_master_delete(mbm_handle));
test_common_task_notify_done(xTaskGetCurrentTaskHandle());
vTaskSuspend(NULL);
}
TaskHandle_t master_tcp_create_instance(mb_communication_info_t *pconfig, uint32_t priority, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size)
{
if (!pconfig || !pdescr) {
ESP_LOGI(TAG, "invalid master configuration.");
}
void *mbm_handle = NULL;
TaskHandle_t master_task_handle = NULL;
TEST_ESP_OK(mbc_master_create_tcp(pconfig, &mbm_handle));
mbm_controller_iface_t *pbase = mbm_handle;
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, pdescr, descr_size));
ESP_LOGI(TAG, "%p, Modbus master stack initialized", mbm_handle);
TEST_ESP_OK(mbc_master_start(mbm_handle));
ESP_LOGI(TAG, "%p, modbus master start...", mbm_handle) ;
if (priority) {
priority = TEST_TASK_PRIO_MASTER;
}
char *port_name = pbase->mb_base->descr.parent_name;
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(func_task_master, port_name,
TEST_TASK_STACK_SIZE,
mbm_handle, TEST_TASK_PRIO_MASTER,
&master_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(master_task_handle, mbm_handle);
return master_task_handle;
}
void app_main(void)
{
void *pnetif = NULL;
TEST_ASSERT_TRUE(test_tcp_services_init(&pnetif) == ESP_OK);
TEST_ASSERT_NOT_NULL(pnetif);
test_common_start();
ESP_LOGI(TAG, "Master TCP is started.");
// Initialize and start Modbus controller
mb_communication_info_t tcp_master_cfg_1 = {
.tcp_opts.port = TEST_TCP_PORT_NUM1,
.tcp_opts.mode = MB_TCP,
.tcp_opts.addr_type = MB_IPV4,
.tcp_opts.ip_addr_table = (void *)slave_tcp_addr_table,
.tcp_opts.uid = 0,
.tcp_opts.start_disconnected = false,
.tcp_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
.tcp_opts.test_tout_us = TEST_TCP_MASTER_SEND_TOUT_US,
.tcp_opts.ip_netif_ptr = pnetif
};
TEST_ASSERT_NOT_NULL(master_tcp_create_instance(&tcp_master_cfg_1, 0, &descriptors[0], num_descriptors));
TEST_ASSERT_EQUAL(test_common_task_start_all(),
test_common_task_wait_done_delete_all(TEST_TCP_TASK_TIMEOUT_MS));
test_common_stop();
test_tcp_services_destroy();
ESP_LOGI(TAG, "Master TCP is completed. (%s).", __func__);
}
@@ -0,0 +1,31 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE=4096
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
CONFIG_ETH_USE_SPI_ETHERNET=n
@@ -0,0 +1,24 @@
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=502
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
# Avoid CI issues "Warning: The smallest app partition is nearly full (5% free space left)!"
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# Enable debug logging
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
@@ -0,0 +1,27 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
#
# Modbus configuration
#
CONFIG_UNITY_ENABLE_FIXTURE=y
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_PORT_ADAPTER_EN=n
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
# Avoid CI issues "Warning: The smallest app partition is nearly full (5% free space left)!"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
# Enable debug logging
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
@@ -0,0 +1,17 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
# Exclude old component feemodbus which exists in old versions
set(EXCLUDE_COMPONENTS freemodbus)
set(EXTRA_COMPONENT_DIRS "../../test_common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# The workaround for the test_utils under ESP-IDF v6.0
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.5")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
else()
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
endif()
project(modbus_tcp_slave_instances)
@@ -0,0 +1,4 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
This test app is a application to test multi-instance modbus communication.
@@ -0,0 +1,6 @@
set(PROJECT_NAME "modbus_tcp_slave_instances")
idf_component_register(SRCS "tcp_slave_instances.c"
INCLUDE_DIRS "."
PRIV_REQUIRES test_utils test_common unity nvs_flash esp_event esp_eth
)
@@ -0,0 +1,45 @@
menu "Modbus Test Configuration"
config MB_PORT_ADAPTER_EN
bool "Enable Modbus port adapter to substitute hardware layer for test."
default y
help
When option is enabled the port communication layer is substituted by
port adapter layer to allow testing of higher layers without access to physical layer.
config MB_TEST_SLAVE_TASK_PRIO
int "Modbus master test task priority"
range 4 23
default 4
help
Modbus master task priority for the test.
config MB_TEST_MASTER_TASK_PRIO
int "Modbus slave test task priority"
range 4 23
default 4
help
Modbus slave task priority for the test.
config MB_TEST_COMM_CYCLE_COUNTER
int "Modbus test communication cycle counter"
range 10 1000
default 10
help
Modbus communication cycle counter for test.
config MB_TEST_LEAK_WARN_LEVEL
int "Modbus test leak warning level"
range 4 256
default 32
help
Modbus test leak warning level.
config MB_TEST_LEAK_CRITICAL_LEVEL
int "Modbus test leak critical level"
range 4 1024
default 64
help
Modbus test leak critical level.
endmenu
@@ -0,0 +1,11 @@
dependencies:
idf: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
espressif/mdns:
version: "^1.0.0"
mb_example_common:
path: "../../../../tools/mb_example_common"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
@@ -0,0 +1,472 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "test_common.h"
#include "test_utils.h"
#include "esp_err.h"
#include "mbc_slave.h"
#include "mbc_master.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#if MB_MDNS_IS_INCLUDED
#include "mdns.h"
#endif
#include "mbcontroller.h" // for mbcontroller defines and api
#include "modbus_params.h" // for modbus parameters structures
#include "protocol_examples_common.h"
#include "esp_event.h"
#if __has_include("unity_test_utils.h")
// unity test utils are used
#include "unity_test_utils.h"
#else
// Unit_test_app utils from test_utils ("test_utils.h"), v4.4
#define unity_utils_task_delete test_utils_task_delete
#endif
#define TEST_TCP_PORT_NUM1 (1502)
#define TEST_TCP_PORT_NUM2 (502)
#define TEST_TCP_TASK_TIMEOUT_MS (160000)
#define TEST_TCP_SLAVE_SEND_TOUT_US (500)
#define TEST_PAR_INFO_GET_TOUT (10)
#define TEST_TASK_START_TIMEOUT (10000 / portTICK_PERIOD_MS)
#define TEST_TASK_NOTIFY_STOP_TOUT (100 / portTICK_PERIOD_MS)
#define TEST_TASK_TICK_TIME (50 / portTICK_PERIOD_MS)
#define TEST_VALUE (12345) // default test value
// Options can be used as bit masks or parameter limits
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
// Defines below are used to define register start address for each type of Modbus registers
#define HOLD_OFFSET_SLAVE(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
#define INPUT_OFFSET_SLAVE(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
#define MB_REG_DISCRETE_INPUT_START (0x0000)
#define MB_REG_COILS_START (0x0000)
#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET_SLAVE(input_data0)) // register offset input area 0
#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET_SLAVE(input_data4)) // register offset input area 1
#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET_SLAVE(holding_data0))
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET_SLAVE(holding_data4))
#define MB_REG_HOLDING_START_AREA2 (HOLD_OFFSET_SLAVE(holding_u8_a))
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET_SLAVE(holding_area2_end) - HOLD_OFFSET_SLAVE(holding_u8_a)) << 1))
#define MB_CHAN_DATA_MAX_VAL (10)
#define MB_CHAN_DATA_OFFSET (1.1f)
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \
| MB_EVENT_HOLDING_REG_RD \
| MB_EVENT_DISCRETE_RD \
| MB_EVENT_COILS_RD)
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \
| MB_EVENT_COILS_WR)
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
#define MB_TEST_VALUE (12345.0)
// The workaround to statically link the whole test library
__attribute__((unused)) bool mb_test_include_phys_impl_tcp = true;
#define TAG "MODBUS_TCP_COMM_SLAVE_TEST"
static esp_err_t test_tcp_services_init(void **pnetif)
{
esp_err_t result = nvs_flash_init();
if ((result == ESP_ERR_NVS_NO_FREE_PAGES) || (result == ESP_ERR_NVS_NEW_VERSION_FOUND)) {
ESP_ERROR_CHECK(nvs_flash_erase());
result = nvs_flash_init();
}
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_init fail, returns(0x%x).",
(int)result);
result = esp_netif_init();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_init fail, returns(0x%x).",
(int)result);
result = esp_event_loop_create_default();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_event_loop_create_default fail, returns(0x%x).",
(int)result);
#if MB_MDNS_IS_INCLUDED
// Start mdns service and register device
if (mdns_init() != ESP_OK) {
ESP_LOGE(TAG, "initialization of mdns fail.");
};
#endif
// This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
// Read "Establishing Wi-Fi or Ethernet Connection" section in
// examples/protocols/README.md for more information about this function.
result = example_connect();
ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"example_connect fail, returns(0x%x).",
(int)result);
#if CONFIG_EXAMPLE_CONNECT_WIFI
// result = esp_wifi_set_ps(WIFI_PS_NONE);
// ESP_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
// TAG,
// "esp_wifi_set_ps fail, returns(0x%x).",
// (int)result);
#endif
if (pnetif) {
*pnetif = get_example_netif();
}
return ESP_OK;
}
static esp_err_t test_tcp_services_destroy(void)
{
esp_err_t err = ESP_OK;
err = example_disconnect();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"example_disconnect fail, returns(0x%x).",
(int)err);
err = esp_event_loop_delete_default();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_event_loop_delete_default fail, returns(0x%x).",
(int)err);
err = esp_netif_deinit();
ESP_RETURN_ON_FALSE(((err == ESP_OK) || (err == ESP_ERR_NOT_SUPPORTED)),
ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_deinit fail, returns(0x%x).",
(int)err);
err = nvs_flash_deinit();
ESP_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_deinit fail, returns(0x%x).",
(int)err);
#if MB_MDNS_IS_INCLUDED
// Stop mdns service and register device
mdns_free();
#endif
return err;
}
static void func_slave_task(void *arg)
{
void *mbs_handle = arg;
mbs_controller_iface_t *pctrl_obj = ((mbs_controller_iface_t *)mbs_handle);
mb_param_info_t reg_info; // keeps the Modbus registers access information
test_common_task_wait_start_and_stop(TEST_TASK_START_TIMEOUT);
while (1) {
// Get parameter information from parameter queue
esp_err_t err = mbc_slave_get_param_info(mbs_handle, &reg_info, TEST_PAR_INFO_GET_TOUT);
const char *rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
if (test_common_task_wait_start_and_stop(TEST_TASK_NOTIFY_STOP_TOUT)) {
ESP_LOGD(TAG, "Received destroy message, destroying instance: %p.", mbs_handle);
break;
}
// Filter events and process them accordingly
if ((err != ESP_ERR_TIMEOUT) && (reg_info.type & MB_READ_WRITE_MASK)) {
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
// Get parameter information from parameter queue
ESP_LOGI(TAG, "OBJ %p, HOLDING %s (%" PRIu32 " us), SL: %u, REG:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 "(0x%" PRIx16 "), SIZE:%u",
(void *)pctrl_obj->mb_base->descr.parent,
rw_str,
(uint32_t)reg_info.time_stamp,
(unsigned)pctrl_obj->opts.comm_opts.common_opts.uid,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
*(uint16_t *)reg_info.address,
(unsigned)reg_info.size);
if (reg_info.address == (uint8_t *)&holding_reg_params.holding_data0) {
(void)mbc_slave_unlock(mbs_handle);
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
if (holding_reg_params.holding_data0 >= MB_CHAN_DATA_MAX_VAL) {
coil_reg_params.coils_port1 = 0xFF;
ESP_LOGI(TAG, "Reached maximum value");
}
(void)mbc_slave_unlock(mbs_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "OBJ %p, INPUT %s (%" PRIu32 " us), SL: %u, REG:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 "(0x%" PRIx16 "), SIZE:%u",
(void *)pctrl_obj->mb_base->descr.parent,
rw_str,
(uint32_t)reg_info.time_stamp,
(unsigned)pctrl_obj->opts.comm_opts.common_opts.uid,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
*(uint16_t *)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "OBJ %p, DISCRETE %s (%" PRIu32 " us), SL: %u, REG:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 "(0x%" PRIx16 "), SIZE:%u",
(void *)pctrl_obj->mb_base->descr.parent,
rw_str,
(uint32_t)reg_info.time_stamp,
(unsigned)pctrl_obj->opts.comm_opts.common_opts.uid,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
*(uint16_t *)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "OBJ %p, COILS %s (%" PRIu32 " us), SL: %u, REG:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 "(0x%" PRIx16 "), SIZE:%u",
(void *)pctrl_obj->mb_base->descr.parent,
rw_str,
(uint32_t)reg_info.time_stamp,
(unsigned)pctrl_obj->opts.comm_opts.common_opts.uid,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
*(uint16_t *)reg_info.address,
(unsigned)reg_info.size);
}
}
vTaskDelay(TEST_TASK_TICK_TIME); // Let IDLE task to trigger
if (coil_reg_params.coils_port1 == 0xFF) {
ESP_LOGD(TAG, "Stop slave: %p.", mbs_handle);
vTaskDelay(TEST_TASK_TICK_TIME); // Let master to get response from slave prior to close
break;
}
}
ESP_LOGI(TAG, "Destroy slave, inst: %p.", mbs_handle);
TEST_ESP_OK(mbc_slave_delete(mbs_handle));
ESP_LOGD(TAG, "Notify task done, inst: %p.", xTaskGetCurrentTaskHandle());
test_common_task_notify_done(xTaskGetCurrentTaskHandle());
vTaskDelay(10);
vTaskSuspend(NULL);
}
// Set register values into known state
static void setup_reg_data(void)
{
// Define initial state of parameters
discrete_reg_params.discrete_input0 = 1;
discrete_reg_params.discrete_input1 = 0;
discrete_reg_params.discrete_input2 = 1;
discrete_reg_params.discrete_input3 = 0;
discrete_reg_params.discrete_input4 = 1;
discrete_reg_params.discrete_input5 = 0;
discrete_reg_params.discrete_input6 = 1;
discrete_reg_params.discrete_input7 = 0;
holding_reg_params.holding_data0 = 0.34;
holding_reg_params.holding_data1 = 2.56;
holding_reg_params.holding_data2 = 3.78;
holding_reg_params.holding_data3 = 4.90;
holding_reg_params.holding_data4 = 5.67;
holding_reg_params.holding_data5 = 6.78;
holding_reg_params.holding_data6 = 7.79;
holding_reg_params.holding_data7 = 8.80;
#if CONFIG_FMB_EXT_TYPE_SUPPORT
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[0], (uint8_t)0x55);
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[1], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[0], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[1], (uint8_t)0x55);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[1], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[1], (uint16_t)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[0], (float)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[1], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[0], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[1], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[0], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[1], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[0], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[1], (float)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[1], (uint32_t)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[0], (double)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[1], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[0], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[1], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[0], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[1], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[0], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[1], (double)MB_TEST_VALUE);
#endif
coil_reg_params.coils_port0 = 0x55;
coil_reg_params.coils_port1 = 0xAA;
input_reg_params.input_data0 = 1.12;
input_reg_params.input_data1 = 2.34;
input_reg_params.input_data2 = 3.56;
input_reg_params.input_data3 = 4.78;
input_reg_params.input_data4 = 1.12;
input_reg_params.input_data5 = 2.34;
input_reg_params.input_data6 = 3.56;
input_reg_params.input_data7 = 4.78;
}
void instance_slave_setup_start(void *mbs_handle)
{
TEST_ASSERT_TRUE(mbs_handle);
mb_register_area_descriptor_t reg_area;
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
reg_area.address = (void *)&holding_reg_params.holding_data0; // Set pointer to storage instance
reg_area.size = (MB_REG_HOLDING_START_AREA1 - MB_REG_HOLDING_START_AREA0) << 1; // Set the size of register storage instance
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
reg_area.address = (void *)&holding_reg_params.holding_data4; // Set pointer to storage instance
reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
#if CONFIG_FMB_EXT_TYPE_SUPPORT
// The extended parameters register area
reg_area.type = MB_PARAM_HOLDING;
reg_area.start_offset = MB_REG_HOLDING_START_AREA2;
reg_area.address = (void *)&holding_reg_params.holding_u8_a;
reg_area.size = MB_REG_HOLDING_START_AREA2_SIZE;
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
#endif
// Initialization of Input Registers area
reg_area.type = MB_PARAM_INPUT;
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
reg_area.address = (void *)&input_reg_params.input_data0;
reg_area.size = sizeof(float) << 2;
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
reg_area.type = MB_PARAM_INPUT;
reg_area.start_offset = MB_REG_INPUT_START_AREA1;
reg_area.address = (void *)&input_reg_params.input_data4;
reg_area.size = sizeof(float) << 2;
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
// Initialization of Coils register area
reg_area.type = MB_PARAM_COIL;
reg_area.start_offset = MB_REG_COILS_START;
reg_area.address = (void *)&coil_reg_params;
reg_area.size = sizeof(coil_reg_params);
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
// Initialization of Discrete Inputs register area
reg_area.type = MB_PARAM_DISCRETE;
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
reg_area.address = (void *)&discrete_reg_params;
reg_area.size = sizeof(discrete_reg_params);
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
// Set values into known state
setup_reg_data();
// Starts of modbus controller and stack
TEST_ESP_OK(mbc_slave_start(mbs_handle));
}
TaskHandle_t slave_tcp_create_instance(mb_communication_info_t *pconfig, uint32_t priority)
{
if (!pconfig) {
ESP_LOGI(TAG, "invalid slave configuration.");
}
void *mbs_handle = NULL;
TaskHandle_t slave_task_handle = NULL;
TEST_ESP_OK(mbc_slave_create_tcp(pconfig, &mbs_handle));
mbs_controller_iface_t *pbase = mbs_handle;
instance_slave_setup_start(mbs_handle);
if (priority) {
priority = TEST_TASK_PRIO_SLAVE;
}
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(func_slave_task, pbase->mb_base->descr.parent_name,
TEST_TASK_STACK_SIZE,
mbs_handle, priority,
&slave_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(slave_task_handle, mbs_handle);
return slave_task_handle;
}
void app_main(void)
{
void *pnetif = NULL;
TEST_ASSERT_TRUE(test_tcp_services_init(&pnetif) == ESP_OK);
TEST_ASSERT_NOT_NULL(pnetif);
test_common_start();
mb_communication_info_t tcp_slave_cfg_1 = {
.tcp_opts.port = TEST_TCP_PORT_NUM1,
.tcp_opts.mode = MB_TCP,
.tcp_opts.addr_type = MB_IPV4,
.tcp_opts.ip_addr_table = NULL,
.tcp_opts.uid = MB_DEVICE_ADDR1,
.tcp_opts.start_disconnected = true,
.tcp_opts.response_tout_ms = 1,
.tcp_opts.test_tout_us = TEST_TCP_SLAVE_SEND_TOUT_US,
.tcp_opts.ip_netif_ptr = pnetif
};
TEST_ASSERT_NOT_NULL(slave_tcp_create_instance(&tcp_slave_cfg_1, 0));
ESP_LOGI(TAG, "Slave TCP #1 is started. (%s).", __func__);
mb_communication_info_t tcp_slave_cfg_2 = {
.tcp_opts.port = TEST_TCP_PORT_NUM2,
.tcp_opts.mode = MB_TCP,
.tcp_opts.addr_type = MB_IPV4,
.tcp_opts.ip_addr_table = NULL,
.tcp_opts.uid = MB_DEVICE_ADDR2,
.tcp_opts.start_disconnected = true,
.tcp_opts.response_tout_ms = 1,
.tcp_opts.test_tout_us = TEST_TCP_SLAVE_SEND_TOUT_US,
.tcp_opts.ip_netif_ptr = pnetif
};
TEST_ASSERT_NOT_NULL(slave_tcp_create_instance(&tcp_slave_cfg_2, 0));
ESP_LOGI(TAG, "Slave TCP #2 is started. (%s).", __func__);
TEST_ASSERT_EQUAL(test_common_task_start_all(),
test_common_task_wait_done_delete_all(TEST_TCP_TASK_TIMEOUT_MS));
ESP_LOGI(TAG, "Slave TCP test is completed. (%s).", __func__);
test_common_stop();
test_tcp_services_destroy();
}
@@ -0,0 +1,30 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE=4096
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
CONFIG_ETH_USE_SPI_ETHERNET=n
@@ -0,0 +1,24 @@
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=502
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
# Avoid CI issues "Warning: The smallest app partition is nearly full (5% free space left)!"
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# Enable debug logging
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
@@ -0,0 +1,27 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
#
# Modbus configuration
#
CONFIG_UNITY_ENABLE_FIXTURE=y
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
CONFIG_FMB_PORT_TASK_PRIO=10
CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_PORT_ADAPTER_EN=n
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
# Avoid CI issues "Warning: The smallest app partition is nearly full (5% free space left)!"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
# Enable debug logging
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
@@ -0,0 +1,194 @@
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
# main thread in Multi DUT case.
import logging
import os
from typing import Tuple
import pytest
from conftest import (
ModbusTestDut,
Stages,
PARAM_SUCCESS,
PARAM_FAIL,
MASTER_TAG,
SLAVE_TAG,
)
pattern_dict_slave = {
Stages.STACK_IPV4: (
r"I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
),
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (r"I \(([0-9]+)\) [A-Z_]*: (Modbus slave stack initialized)."),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) port.utils: Socket \(#[0-9]+\), accept client connection from address\[port\]: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\[[0-9]+\]"
),
Stages.STACK_START: (r"I\s\(([0-9]+)\) [A-Z_]+: Slave TCP [#0-9]*\s*is started"),
Stages.STACK_PAR_OK: (
r"I\s\(([0-9]+)\) [A-Z_]+: OBJ (0x[a-fA-Z0-9]+),() ([A-Za-z\s]+) \([0-9]+ us\),\s*[A-Z:]*\s*[0-9,]*\s*[A-Z:]*[0-9]*, TYPE:[0-9]+, INST_ADDR:0x[a-fA-Z0-9]+[()0-9a-z]+, SIZE:[0-9]+"
),
Stages.STACK_PAR_FAIL: (
r"E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet"
),
Stages.STACK_DESTROY: (r"I\s\(([0-9]+)\) [A-Z_]+: Destroy slave"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_tcp[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.slave: mbc_[a-z]+_slave_get_parameter: Good response for get cid\(([0-9]+)\) = ESP_OK"
),
}
pattern_dict_master = {
Stages.STACK_IPV4: (
r"I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
),
Stages.STACK_IPV6: (
r"I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
),
Stages.STACK_INIT: (
r"I \(([0-9]+)\) [A-Z_]*: [0xa-f0-9,]*\s*Modbus master stack initialized"
),
Stages.STACK_CONNECT: (
r"I\s\(([0-9]+)\) mb_port.tcp.master: 0x[a-f0-9]+, Connected: [0-9], [0-9], start polling."
),
Stages.STACK_START: (r"I \(([0-9]+)\) [A-Z_]+: Master TCP is started"),
Stages.STACK_PAR_OK: (
r"I \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) ([a-zA-Z0-9\_]+) \([\%a-zA-Z_\/]+\) value = ([0-9a-z.A-Z]*)\s*([a-zA-Z0-9()]*) read successful"
),
Stages.STACK_PAR_FAIL: (
r"E \(([0-9]+)\) [A-Z_]+: ([a-z0-9]+) Characteristic #([0-9]+) \(([a-zA-Z0-9_]+)\) read fail, err = [x0-9]+ \([_A-Z]+\)"
),
Stages.STACK_DESTROY: (r"I \(([0-9]+)\) [A-Z_]+: (Master TCP is completed.)"),
Stages.STACK_OBJECT_CREATE: (
r"D \(([0-9]+)\) [a-z]+_[a-z]+\.([a-z]+)\: created object mb[a-z]\_tcp[#@](0x[0-9a-f]+)"
),
Stages.STACK_BAD_CONNECTION: (
r"I \([0-9]+\) example_connect: WiFi Connect failed [0-9]* times, stop reconnect."
),
Stages.STACK_CID_RESPONSE_TIME: (
r"D \(([0-9]+)\) mbc_[a-z]+.master: mbc_[a-z]+_master_get_parameter: ([a-zA-Z0-9]+) Good response for get cid\(([0-9]+)\) = ESP_OK"
),
}
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = "modbus_test"
CONFORMANCE_TEST_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../tools/robot")
)
logger = logging.getLogger(LOGGER_NAME)
test_configs = [
# 'wifi',
"ethernet"
]
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_tcp
@pytest.mark.parametrize("config", test_configs, indirect=True)
@pytest.mark.parametrize(
"count, app_path",
[
(
2,
f"{os.path.join(os.path.dirname(__file__), 'mb_tcp_slave_instances')}|{os.path.join(os.path.dirname(__file__), 'mb_tcp_master_instances')}",
)
],
indirect=True,
)
def test_modbus_tcp_communication(dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
dut_master = dut[1]
dut_slave = dut[0]
dut_master.add_dut_list(dut_slave)
dut_slave.add_dut_list(dut_master)
logger.info("DUT: %s start.", dut_master.dut_get_name())
logger.info("DUT: %s start.", dut_slave.dut_get_name())
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
### Slave and Master objects registered
slave_objects = dut_slave.get_objects_by_tag(SLAVE_TAG)
for object in slave_objects:
logger.info("Modbus slave objects: %s", object)
logger.info("Number of slave objects: %s", len(slave_objects))
master_objects = dut_master.get_objects_by_tag(MASTER_TAG)
for object in master_objects:
logger.info("Modbus master objects: %s", object)
logger.info("Number of master objects: %s", len(master_objects))
params_timestamp = dut_master.get_params_by_timestamp_range(40000, 45000)
if params_timestamp:
for param in params_timestamp:
logger.info("Modbus params by timestamp: %s", param)
### Slave Fail and Success Params
slave_success_params = dut_slave.get_slave_params_by_status(PARAM_SUCCESS)
logger.info("Total successful slave parameters: %d", len(slave_success_params))
slave_fail_params = dut_slave.get_slave_params_by_status(PARAM_FAIL)
logger.info("Total fail slave parameters: %d", len(slave_fail_params))
all_slave_params = len(slave_success_params) + len(slave_fail_params)
if all_slave_params:
logger.info(
"All slave parameters: %s, Ratio of successful slave parameters: %s",
all_slave_params,
(len(slave_success_params) / all_slave_params) * 100,
)
### Master Fail and Success Params
master_success_params = dut_master.get_master_params_by_status(PARAM_SUCCESS)
logger.info("Total successful master parameters: %d", len(master_success_params))
master_fail_params = dut_master.get_master_params_by_status(PARAM_FAIL)
logger.info("Total fail master parameters: %d", len(master_fail_params))
all_master_params = len(master_success_params) + len(master_fail_params)
if all_master_params:
logger.info(
"All master parameters: %s, Ratio of successful master parameters: %s",
all_master_params,
(len(master_success_params) / all_master_params) * 100,
)
### Master average response time for successful requests
logger.info(
"Average response time for successful master requests: %d ms",
dut_master.get_avg_response_time_master(),
)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()
### Histogram Modbus stats
dut_master.plot_modbus_stats(
master_objects,
dut_master.dut_stats_info(),
slave_objects,
dut_slave.dut_stats_info(),
"modbus_stats_graph",
)
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_generic
@pytest.mark.parametrize("config", ["dummy_config"])
def test_modbus_tcp_generic(config) -> None:
logger.info("The generic tcp example tests are not provided yet.")
if __name__ == "__main__":
pytest.main(["pytest_mb_tcp_instances.py"])
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.22)
idf_component_register(SRCS "test_common.c"
INCLUDE_DIRS "include"
REQUIRES unity)
REQUIRES unity esp_timer)
set(EXTRA_COMPONENT_DIRS)
+21 -5
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,6 +15,11 @@
#include "sdkconfig.h"
#define TEST_TASK_PRIO_MASTER (CONFIG_MB_TEST_MASTER_TASK_PRIO)
#define TEST_TASK_PRIO_SLAVE (CONFIG_MB_TEST_SLAVE_TASK_PRIO)
#define TEST_TASK_STACK_SIZE (5120)
#define STR(fieldname) ((const char *)(fieldname))
#define OPTS(min_val, max_val, step_val) \
{ \
@@ -23,7 +28,7 @@
// Enumeration of modbus slave addresses accessed by master device
enum {
MB_DEVICE_ADDR1 = 1,
MB_DEVICE_ADDR1 = 01,
MB_DEVICE_ADDR2 = 200
};
@@ -44,6 +49,12 @@ enum {
TEST_REG_VAL4 = 0x4444
};
// Enumeration of task notification
enum {
TASK_START = 1,
TASK_STOP = 2
};
typedef struct task_entry_s {
TaskHandle_t task_handle;
SemaphoreHandle_t task_sema_handle;
@@ -87,12 +98,17 @@ esp_err_t test_common_write_modbus_parameter(void *handle, uint16_t cid, uint16_
* @brief The test helper functions to work with test tasks
*
*/
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value);
void test_common_task_notify_start_and_stop(TaskHandle_t task_handle, uint32_t value);
void test_common_task_start(TaskHandle_t task_handle, uint32_t value);
int test_common_task_start_all(uint32_t value);
uint32_t test_common_task_wait_start_and_stop(TickType_t timeout_ticks);
int test_common_task_start_all();
bool test_common_task_wait_done(TaskHandle_t task_handle, TickType_t timeout_ticks);
bool test_common_task_wait_done_delete(TaskHandle_t task_handle, TickType_t task_timeout_ticks);
int test_common_task_wait_done_delete_all(TickType_t task_timeout_tick);
void test_common_task_delete(TaskHandle_t task_handle);
void test_common_task_delete_all();
int test_common_task_delete_all();
void *test_common_task_get_instance(TaskHandle_t task_handle);
void test_task_add_entry(TaskHandle_t task_handle, void *pinst);
void test_common_task_notify_done(TaskHandle_t task_handle);
void test_common_task_destroy_and_notify_done_all();
bool test_common_wait_check_destroy_message(char *message, uint32_t timeout_ms);
+84 -23
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,7 @@
#include "mb_common.h"
#include "mbc_slave.h"
#include "mbc_master.h"
#include "esp_timer.h" // for esp_timer_get_time()
#include "test_common.h"
#include "esp_heap_caps.h"
@@ -21,9 +22,6 @@
#include "esp_heap_trace.h"
#endif
#define TEST_TASK_PRIO_MASTER (CONFIG_MB_TEST_MASTER_TASK_PRIO)
#define TEST_TASK_PRIO_SLAVE (CONFIG_MB_TEST_SLAVE_TASK_PRIO)
#define TEST_TASK_STACK_SIZE (5120)
#define TEST_TASK_CYCLE_COUNTER (CONFIG_MB_TEST_COMM_CYCLE_COUNTER)
#define TEST_BUSY_TASK_PRIO (20)
@@ -42,8 +40,11 @@
#define TEST_NOTIF_SIZE (20)
#define TEST_ALLOW_PROC_FAIL (5) // percentage of allowed failures due to desynchronization
#define TEST_TASK_TICK_TIME (50 / portTICK_PERIOD_MS)
#define TEST_DESTROY_MESSAGE_TOUT (500)
#define TEST_NOTIFY_DONE_TOUT (200 / portTICK_PERIOD_MS)
#define TAG "TEST_COMMON"
#define MSG_DESTROY "Destroy instances\n\0"
typedef enum {
RT_HOLDING_RD,
@@ -109,18 +110,18 @@ static bool task_wait_done_and_remove(task_entry_t *task_entry, TickType_t tout_
if ((xSemaphoreTake(task_entry->task_sema_handle, tout_ticks) == pdTRUE)) {
ESP_LOGI(TAG, "Test task 0x%" PRIx32 ", done successfully.", (uint32_t)task_entry->task_handle);
is_done = true;
} else {
ESP_LOGE(TAG, "Could not complete task 0x%" PRIx32 " after timeout, force kill the task.",
(uint32_t)task_entry->task_handle);
is_done = false;
task_entry_remove(task_entry);
}
// Else statement removed from here.
//This is a workaround on how to handle the finishing of tasks without increasing timeout of
//test_common_task_wait_done_delete_all() and task_wait_done_and_remove()
vTaskDelay(1); // Let the lower priority task to suspend or delete itself
task_entry_remove(task_entry);
}
return (is_done);
}
static void test_task_add_entry(TaskHandle_t task_handle, void *inst)
void test_task_add_entry(TaskHandle_t task_handle, void *inst)
{
TEST_ASSERT_TRUE(task_handle);
task_entry_t *new_entry = (task_entry_t *) calloc(1, sizeof(task_entry_t));
@@ -154,7 +155,42 @@ static task_entry_t *test_task_find_entry(TaskHandle_t task_handle)
return pfound;
}
static void test_common_task_notify_done(TaskHandle_t task_handle)
void test_common_task_notify_stop_all()
{
task_entry_t *it = NULL;
if (LIST_EMPTY(&s_task_list)) {
return;
}
LIST_FOREACH(it, &s_task_list, entries) {
ESP_LOGD(TAG, "Notify task stop, inst: %p.", it->task_handle);
test_common_task_notify_start_and_stop(it->task_handle, TASK_STOP);
}
return;
}
bool test_common_wait_check_destroy_message(char *message, uint32_t timeout_ms)
{
/* Read line from console, non-blocking function, timeout in ms */
char buffer[64] = {0}; //fixed size buffer to store the input from stdin
ESP_LOGD(TAG, "Waiting for destroy message: \"%s\" (timeout: %lu ms)", message, timeout_ms);
vTaskDelay(pdMS_TO_TICKS(timeout_ms));
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
ESP_LOGD(TAG, "%s: received: %s", __func__, buffer);
}
if (strcmp(buffer, message) == 0) {
ESP_LOGD(TAG, "Destroy message matched, notifying to destroy instances.");
return true;
}
ESP_LOGD(TAG, "Timeout waiting for destroy message.");
return false;
}
void test_common_task_notify_done(TaskHandle_t task_handle)
{
task_entry_t *it = test_task_find_entry(task_handle);
if (it) {
@@ -180,10 +216,10 @@ static void test_busy_task(void *phandle)
void test_common_task_start(TaskHandle_t task_handle, uint32_t value)
{
// Directly notify the task waiting to start loop
test_common_task_notify_start(task_handle, value);
test_common_task_notify_start_and_stop(task_handle, value);
}
int test_common_task_start_all(uint32_t value)
int test_common_task_start_all()
{
task_entry_t *it = NULL;
if (LIST_EMPTY(&s_task_list)) {
@@ -191,7 +227,7 @@ int test_common_task_start_all(uint32_t value)
}
int task_count = 0;
LIST_FOREACH(it, &s_task_list, entries) {
test_common_task_notify_start(it->task_handle, value);
test_common_task_notify_start_and_stop(it->task_handle, TASK_START);
task_count++;
}
return task_count;
@@ -216,13 +252,32 @@ int test_common_task_wait_done_delete_all(TickType_t task_timeout_tick)
{
task_entry_t *it, *ptmp = NULL;
int task_count = 0;
int64_t start_time = esp_timer_get_time(); //getting the reference system time to compare
if (LIST_EMPTY(&s_task_list)) {
return 0;
}
LIST_FOREACH_SAFE(it, &s_task_list, entries, ptmp) {
task_wait_done_and_remove(it, task_timeout_tick);
task_count++;
while ((esp_timer_get_time() - start_time) <= (task_timeout_tick * portTICK_PERIOD_MS * 1000)) {
if (LIST_EMPTY(&s_task_list)) {
return task_count;
}
if (test_common_wait_check_destroy_message(MSG_DESTROY, TEST_DESTROY_MESSAGE_TOUT)) {
test_common_task_notify_stop_all();
}
LIST_FOREACH_SAFE(it, &s_task_list, entries, ptmp) {
if (task_wait_done_and_remove(it, TEST_NOTIFY_DONE_TOUT)) {
task_count++;
}
}
vTaskDelay(TEST_TASK_TICK_TIME);
}
task_count = test_common_task_delete_all();
return task_count;
}
@@ -234,12 +289,15 @@ void test_common_task_delete(TaskHandle_t task_handle)
}
}
void test_common_task_delete_all()
int test_common_task_delete_all()
{
task_entry_t *it = NULL;
int task_count = 0;
while ((it = LIST_FIRST(&s_task_list))) {
task_entry_remove(it);
task_count++;
}
return task_count;
}
void *test_common_task_get_instance(TaskHandle_t task_handle)
@@ -305,7 +363,7 @@ void test_common_stop()
CONFIG_MB_TEST_LEAK_WARN_LEVEL, CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL);
}
static uint32_t test_common_task_wait_start(TickType_t timeout_ticks)
uint32_t test_common_task_wait_start_and_stop(TickType_t timeout_ticks)
{
static uint32_t notify_value = 0;
@@ -317,9 +375,10 @@ static uint32_t test_common_task_wait_start(TickType_t timeout_ticks)
return 0;
}
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value)
void test_common_task_notify_start_and_stop(TaskHandle_t task_handle, uint32_t value)
{
ESP_LOGD(TAG, "Notify task start 0x%" PRIx32, (uint32_t)task_handle);
char *task_state = (value == TASK_START) ? "start" : "stop";
ESP_LOGD(TAG, "Notify task %s 0x%" PRIx32, task_state, (uint32_t)task_handle);
TEST_ASSERT_EQUAL_INT(xTaskNotify(task_handle, value, eSetValueWithOverwrite), pdTRUE);
}
@@ -419,7 +478,7 @@ static void test_master_task(void *arg)
uint16_t cycle_counter = 0;
// Wait task start notification during timeout
test_common_task_wait_start(TEST_TASK_START_TIMEOUT);
test_common_task_wait_start_and_stop(TEST_TASK_START_TIMEOUT);
holding_registers[CID_DEV_REG0] = TEST_REG_VAL1;
holding_registers[CID_DEV_REG1] = TEST_REG_VAL2;
@@ -485,7 +544,7 @@ static void test_slave_task(void *arg)
mbs_controller_iface_t *pctrl_obj = ((mbs_controller_iface_t *)mbs_handle);
mb_param_info_t reg_info; // keeps the Modbus registers access information
test_common_task_wait_start(TEST_TASK_START_TIMEOUT);
test_common_task_wait_start_and_stop(TEST_TASK_START_TIMEOUT);
while (1) {
// Get parameter information from parameter queue
@@ -513,6 +572,7 @@ static void test_slave_task(void *arg)
break;
}
}
ESP_LOGI(TAG, "Destroy slave, inst: %p.", mbs_handle);
TEST_ESP_OK(mbc_slave_delete(mbs_handle));
ESP_LOGD(TAG, "Notify task done, inst: %p.", xTaskGetCurrentTaskHandle());
@@ -544,6 +604,7 @@ void test_common_slave_setup_start(void *mbs_handle)
reg_area.size = coil_registers_counter;
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
TEST_ESP_OK(mbc_slave_start(mbs_handle));
}
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
+5
View File
@@ -0,0 +1,5 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
idf_component_register(SRCS "modbus_params.c"
INCLUDE_DIRS "include")
+10
View File
@@ -0,0 +1,10 @@
# Modbus Example Common
This directory contains component that is common for Modbus master and slave examples. The component defines Modbus parameters that are shared between examples and provide code that you can copy and adapt into your own projects.
For more information please refer to Modbus example README.md files located in the folders:
* `examples/protocols/modbus/serial/mb_master` Modbus serial master implementation (RTU and ASCII)
* `examples/protocols/modbus/serial/mb_slave` Modbus serial slave implementation (RTU and ASCII)
* `examples/protocols/modbus/serial/mb_master` Modbus serial master implementation (RTU and ASCII)
* `examples/protocols/modbus/tcp/mb_tcp_slave` Modbus serial slave implementation (TCP)
* `examples/protocols/modbus/tcp/mb_tcp_master` Modbus serial master implementation (TCP)
+5
View File
@@ -0,0 +1,5 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .
@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*=====================================================================================
* Description:
* The Modbus parameter structures used to define Modbus instances that
* can be addressed by Modbus protocol. Define these structures per your needs in
* your application. Below is just an example of possible parameters.
*====================================================================================*/
#ifndef _DEVICE_PARAMS
#define _DEVICE_PARAMS
#include <stdint.h>
#include "sdkconfig.h"
// This file defines structure of modbus parameters which reflect correspond modbus address space
// for each modbus register type (coils, discrete inputs, holding registers, input registers)
#pragma pack(push, 1)
typedef struct {
uint8_t discrete_input0: 1;
uint8_t discrete_input1: 1;
uint8_t discrete_input2: 1;
uint8_t discrete_input3: 1;
uint8_t discrete_input4: 1;
uint8_t discrete_input5: 1;
uint8_t discrete_input6: 1;
uint8_t discrete_input7: 1;
uint8_t discrete_input_port1;
uint8_t discrete_input_port2;
} discrete_reg_params_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint8_t coils_port0;
uint8_t coils_port1;
uint8_t coils_port2;
} coil_reg_params_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
float input_data0; // 0
float input_data1; // 2
float input_data2; // 4
float input_data3; // 6
uint16_t data[150]; // 8 + 150 = 158
float input_data4; // 158
float input_data5;
float input_data6;
float input_data7;
uint16_t data_block1[150];
} input_reg_params_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
#if CONFIG_FMB_EXT_TYPE_SUPPORT
uint16_t holding_u8_a[2];
uint16_t holding_u8_b[2];
uint16_t holding_u16_ab[2];
uint16_t holding_u16_ba[2];
uint32_t holding_uint32_abcd[2];
uint32_t holding_uint32_cdab[2];
uint32_t holding_uint32_badc[2];
uint32_t holding_uint32_dcba[2];
float holding_float_abcd[2];
float holding_float_cdab[2];
float holding_float_badc[2];
float holding_float_dcba[2];
double holding_double_abcdefgh[2];
double holding_double_hgfedcba[2];
double holding_double_ghefcdab[2];
double holding_double_badcfehg[2];
uint32_t holding_area2_end;
#endif
float holding_data0;
float holding_data1;
float holding_data2;
float holding_data3;
uint16_t test_regs[150];
float holding_data4;
float holding_data5;
float holding_data6;
float holding_data7;
uint32_t holding_area1_end;
} holding_reg_params_t;
#pragma pack(pop)
extern holding_reg_params_t holding_reg_params;
extern input_reg_params_t input_reg_params;
extern coil_reg_params_t coil_reg_params;
extern discrete_reg_params_t discrete_reg_params;
#endif // !defined(_DEVICE_PARAMS)
+21
View File
@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*=====================================================================================
* Description:
* C file to define parameter storage instances
*====================================================================================*/
#include <stdint.h>
#include "modbus_params.h"
// Here are the user defined instances for device parameters packed by 1 byte
// These are keep the values that can be accessed from Modbus master
holding_reg_params_t holding_reg_params = { 0 };
input_reg_params_t input_reg_params = { 0 };
coil_reg_params_t coil_reg_params = { 0 };
discrete_reg_params_t discrete_reg_params = { 0 };
+6 -3
View File
@@ -1,7 +1,10 @@
pytest
pytest-embedded
pytest-embedded>=1.18.2
pytest-embedded-idf
pytest-embedded-serial
pytest-embedded-serial>=1.18.2
pytest-embedded-serial-esp
robotframework
scapy>=2.6.0
dataclasses
scapy>=2.6.1
numpy>=1.21.6
matplotlib>=3.5.3