forked from espressif/esp-modbus
Compare commits
4 Commits
idf-extrac
...
feature/up
Author | SHA1 | Date | |
---|---|---|---|
ed7d923184 | |||
6b5430a18f | |||
811f251496 | |||
b7ffdde8fe |
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
.config
|
||||
*.o
|
||||
*.pyc
|
||||
|
||||
# gtags
|
||||
GTAGS
|
||||
GRTAGS
|
||||
GPATH
|
||||
|
||||
# emacs
|
||||
.dir-locals.el
|
||||
|
||||
# emacs temp file suffixes
|
||||
*~
|
||||
.#*
|
||||
\#*#
|
||||
|
||||
# eclipse setting
|
||||
.settings
|
||||
|
||||
# MacOS directory files
|
||||
.DS_Store
|
||||
|
||||
# Test files
|
||||
test/build
|
||||
test/sdkconfig
|
||||
test/sdkconfig.old
|
||||
|
||||
# Doc build artifacts
|
||||
docs/_build/
|
||||
docs/doxygen-warning-log.txt
|
||||
docs/sphinx-warning-log.txt
|
||||
docs/sphinx-warning-log-sanitized.txt
|
||||
docs/xml/
|
||||
docs/xml_in/
|
||||
docs/man/
|
||||
docs/doxygen_sqlite3.db
|
||||
|
||||
TEST_LOGS
|
||||
|
||||
|
||||
# gcov coverage reports
|
||||
*.gcda
|
||||
*.gcno
|
||||
coverage.info
|
||||
coverage_report/
|
||||
|
||||
# VS Code Settings
|
||||
.vscode/
|
123
.gitlab-ci.yml
Normal file
123
.gitlab-ci.yml
Normal file
@ -0,0 +1,123 @@
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
# System environment
|
||||
ESP_DOCS_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env-v5.0:2-2"
|
||||
ESP_DOCS_PATH: "$CI_PROJECT_DIR"
|
||||
|
||||
# GitLab-CI environment
|
||||
GET_SOURCES_ATTEMPTS: "10"
|
||||
ARTIFACT_DOWNLOAD_ATTEMPTS: "10"
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
ESP_IDF_GIT: "https://gitlab-ci-token:${CI_JOB_TOKEN}@${GITLAB_HTTPS_SERVER}/espressif/esp-idf.git"
|
||||
|
||||
.setup_idf_tools: &setup_idf_tools |
|
||||
tools/idf_tools.py --non-interactive install && eval "$(tools/idf_tools.py --non-interactive export)" || exit 1
|
||||
|
||||
.add_gh_key_remote: &add_gh_key_remote |
|
||||
command -v ssh-agent >/dev/null || exit 1
|
||||
eval $(ssh-agent -s)
|
||||
printf '%s\n' "${GH_PUSH_KEY}" | tr -d '\r' | ssh-add - > /dev/null
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config || ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
git remote remove github || true
|
||||
git remote add github ${GH_PUSH_REPO}
|
||||
|
||||
after_script:
|
||||
# Just for cleaning space, no other causes
|
||||
- git clean -ffdx
|
||||
|
||||
|
||||
# This template gets expanded multiple times, once for every IDF version.
|
||||
# IDF version is specified by setting the espressif/idf image tag.
|
||||
#
|
||||
# EXAMPLE_TARGETS sets the list of IDF_TARGET values to build examples for.
|
||||
# It should be equal to the list of targets supported by the specific IDF version.
|
||||
#
|
||||
# TEST_TARGETS sets the list of IDF_TARGET values to build the test_app for.
|
||||
# It should contain only the targets with optimized assembly implementations.
|
||||
#
|
||||
.build_template:
|
||||
stage: build
|
||||
tags:
|
||||
- build
|
||||
- internet
|
||||
script:
|
||||
- ./build_all.sh
|
||||
variables:
|
||||
EXAMPLE_TARGETS: "esp32"
|
||||
TEST_TARGETS: "esp32"
|
||||
|
||||
build_idf_v4.1:
|
||||
extends: .build_template
|
||||
image: espressif/idf:release-v4.1
|
||||
|
||||
build_idf_v4.2:
|
||||
extends: .build_template
|
||||
image: espressif/idf:release-v4.2
|
||||
variables:
|
||||
EXAMPLE_TARGETS: "esp32 esp32s2"
|
||||
|
||||
build_idf_v4.3:
|
||||
extends: .build_template
|
||||
image: espressif/idf:release-v4.3
|
||||
variables:
|
||||
EXAMPLE_TARGETS: "esp32 esp32s2 esp32c3"
|
||||
|
||||
build_idf_v4.4:
|
||||
extends: .build_template
|
||||
image: espressif/idf:release-v4.4
|
||||
variables:
|
||||
EXAMPLE_TARGETS: "esp32 esp32s2 esp32s3 esp32c3"
|
||||
TEST_TARGETS: "esp32 esp32s3"
|
||||
|
||||
build_idf_latest:
|
||||
extends: .build_template
|
||||
image: espressif/idf:latest
|
||||
variables:
|
||||
EXAMPLE_TARGETS: "esp32 esp32s2 esp32s3 esp32c3"
|
||||
TEST_TARGETS: "esp32 esp32s3"
|
||||
# GNU Make based build system is not supported starting from IDF v5.0
|
||||
SKIP_GNU_MAKE_BUILD: 1
|
||||
|
||||
push_master_to_github:
|
||||
stage: deploy
|
||||
tags:
|
||||
- deploy
|
||||
only:
|
||||
- master
|
||||
- /^release\/v/
|
||||
- /^v\d+\.\d+(\.\d+)?($|-)/
|
||||
when: on_success
|
||||
script:
|
||||
- git clone --depth 1 ${ESP_IDF_GIT} esp-idf
|
||||
- *add_gh_key_remote
|
||||
- esp-idf/tools/ci/push_to_github.sh
|
||||
|
||||
upload_to_component_manager:
|
||||
stage: deploy
|
||||
tags:
|
||||
- deploy
|
||||
only:
|
||||
- master
|
||||
- /^release\/v/
|
||||
- /^v\d+\.\d+(\.\d+)?($|-)/
|
||||
when: on_success
|
||||
script:
|
||||
- git clone --depth 1 ${ESP_IDF_GIT} esp-idf
|
||||
- export IDF_PATH=$PWD/esp-idf
|
||||
- cd esp-idf
|
||||
- ./tools/idf_tools.py --non-interactive install-python-env
|
||||
- ./tools/idf_tools.py --non-interactive install
|
||||
- idf_exports=$(${IDF_PATH}/tools/idf_tools.py --non-interactive export)
|
||||
- eval "${idf_exports}"
|
||||
- cd ..
|
||||
- git clone $PWD esp-modbus
|
||||
- cd esp-modbus
|
||||
- pip install --upgrade idf-component-manager
|
||||
- idf.py --help
|
||||
- export IDF_COMPONENT_API_TOKEN=${ESP_MODBUS_API_KEY}
|
||||
- idf.py upload-component --name=esp-modbus --namespace=espressif || true
|
@ -1,3 +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
|
||||
set(srcs
|
||||
"common/esp_modbus_master.c"
|
||||
"common/esp_modbus_slave.c"
|
||||
@ -44,14 +46,20 @@ set(srcs
|
||||
set(include_dirs common/include)
|
||||
|
||||
set(priv_include_dirs common port modbus modbus/ascii modbus/functions
|
||||
modbus/rtu modbus/tcp modbus/include)
|
||||
modbus/rtu modbus/tcp modbus/include)
|
||||
|
||||
list(APPEND priv_include_dirs serial_slave/port serial_slave/modbus_controller
|
||||
serial_master/port serial_master/modbus_controller
|
||||
tcp_slave/port tcp_slave/modbus_controller
|
||||
tcp_master/port tcp_master/modbus_controller)
|
||||
serial_master/port serial_master/modbus_controller
|
||||
tcp_slave/port tcp_slave/modbus_controller
|
||||
tcp_master/port tcp_master/modbus_controller)
|
||||
|
||||
add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${srcs})
|
||||
add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${include_dirs})
|
||||
add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${priv_include_dirs})
|
||||
|
||||
message(STATUS "DEBUG: Use esp-modbus component folder: ${CMAKE_CURRENT_LIST_DIR}.")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "${include_dirs}"
|
||||
PRIV_INCLUDE_DIRS "${priv_include_dirs}"
|
||||
REQUIRES driver)
|
||||
REQUIRES driver lwip)
|
92
build_all.sh
Executable file
92
build_all.sh
Executable file
@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the test app and all examples from the examples directory.
|
||||
# Expects TEST_TARGETS environment variables to be set.
|
||||
# Each variable is the list of IDF_TARGET values to build the examples and
|
||||
# the test app for, respectively.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Safety settings (see https://gist.github.com/ilg-ul/383869cbb01f61a51c4d).
|
||||
|
||||
if [[ -n "${DEBUG_SHELL}" ]]
|
||||
then
|
||||
set -x # Activate the expand mode if DEBUG is anything but empty.
|
||||
fi
|
||||
|
||||
if [[ -z "${EXAMPLE_TARGETS}" || -z "${TEST_TARGETS}" ]]
|
||||
then
|
||||
echo "EXAMPLE_TARGETS and TEST_TARGETS environment variables must be set before calling this script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${SKIP_GNU_MAKE_BUILD}" ]]
|
||||
then
|
||||
echo "SKIP_GNU_MAKE_BUILD not set, will build with GNU Make based build system as well."
|
||||
export SKIP_GNU_MAKE_BUILD=0
|
||||
fi
|
||||
|
||||
set -o errexit # Exit if command failed.
|
||||
set -o pipefail # Exit if pipe failed.
|
||||
set -o nounset # Exit if variable not set.
|
||||
|
||||
|
||||
STARS='***************************************************'
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
die() {
|
||||
echo "${1:-"Unknown Error"}" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# build_for_targets <target list>
|
||||
# call this in the project directory
|
||||
function build_for_targets
|
||||
{
|
||||
target_list="$1"
|
||||
for IDF_TARGET in ${target_list}
|
||||
do
|
||||
export IDF_TARGET
|
||||
if [[ "${IDF_TARGET}" = "esp32" ]] && [[ "${SKIP_GNU_MAKE_BUILD}" = "0" ]]
|
||||
then
|
||||
echo "${STARS}"
|
||||
echo "Building in $PWD with Make"
|
||||
# -j option will be set via MAKEFLAGS in .gitlab-ci.yml
|
||||
# shellcheck disable=SC2015
|
||||
make defconfig && make || die "Make build in ${PWD} has failed"
|
||||
rm -rf build
|
||||
fi
|
||||
|
||||
echo "${STARS}"
|
||||
echo "Building in $PWD with CMake for ${IDF_TARGET}"
|
||||
if [[ ${IDF_TARGET} != "esp32" ]]
|
||||
then
|
||||
# IDF 4.0 doesn't support idf.py set-target, and only supports esp32.
|
||||
idf.py set-target "${IDF_TARGET}"
|
||||
fi
|
||||
idf.py build || die "CMake build in ${PWD} has failed for ${IDF_TARGET}"
|
||||
idf.py fullclean
|
||||
done
|
||||
}
|
||||
|
||||
function build_folders
|
||||
{
|
||||
pushd "$1"
|
||||
EXAMPLES=$(find . -maxdepth 1 -mindepth 1 -type d | cut -d '/' -f 2)
|
||||
for NAME in ${EXAMPLES}
|
||||
do
|
||||
cd "${NAME}"
|
||||
build_for_targets "$2"
|
||||
cd ..
|
||||
done
|
||||
popd
|
||||
}
|
||||
|
||||
echo "${STARS}"
|
||||
# Build the tests
|
||||
build_folders test/serial "${TEST_TARGETS}"
|
||||
echo "${STARS}"
|
||||
# Build the tests
|
||||
build_folders test/tcp "${TEST_TARGETS}"
|
||||
echo "${STARS}"
|
||||
|
29
component.mk
Normal file
29
component.mk
Normal file
@ -0,0 +1,29 @@
|
||||
INCLUDEDIRS := common/include
|
||||
PRIV_INCLUDEDIRS := common port modbus modbus/ascii modbus/functions
|
||||
PRIV_INCLUDEDIRS += modbus/rtu modbus/tcp modbus/include
|
||||
PRIV_INCLUDEDIRS += serial_slave/port serial_slave/modbus_controller
|
||||
PRIV_INCLUDEDIRS += serial_master/port serial_master/modbus_controller
|
||||
PRIV_INCLUDEDIRS += tcp_slave/port tcp_slave/modbus_controller
|
||||
PRIV_INCLUDEDIRS += tcp_master/port tcp_master/modbus_controller
|
||||
SRCDIRS := common
|
||||
SRCDIRS += modbus modbus/ascii modbus/functions modbus/rtu modbus/tcp
|
||||
SRCDIRS += serial_slave/port serial_slave/modbus_controller
|
||||
SRCDIRS += serial_master/port serial_master/modbus_controller
|
||||
SRCDIRS += tcp_slave/port tcp_slave/modbus_controller
|
||||
SRCDIRS += tcp_master/port tcp_master/modbus_controller
|
||||
SRCDIRS += port
|
||||
|
||||
COMPONENT_PRIV_INCLUDEDIRS = $(addprefix freemodbus/, \
|
||||
$(PRIV_INCLUDEDIRS) \
|
||||
)
|
||||
|
||||
COMPONENT_SRCDIRS = $(addprefix freemodbus/, \
|
||||
$(SRCDIRS) \
|
||||
)
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS = $(addprefix freemodbus/, \
|
||||
$(INCLUDEDIRS) \
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,82 +0,0 @@
|
||||
# Modbus Master-Slave Example
|
||||
|
||||
## Overview
|
||||
|
||||
These two projects illustrate the communication between Modbus master and slave device in the segment.
|
||||
Master initializes Modbus interface driver and then reads parameters from slave device in the segment.
|
||||
After several successful read attempts slave sets the alarm relay (end of test condition).
|
||||
Once master reads the alarm it stops communication and destroy driver.
|
||||
|
||||
The examples:
|
||||
|
||||
* `examples/protocols/modbus/serial/mb_master` - Modbus serial master ASCII/RTU
|
||||
* `examples/protocols/modbus/serial/mb_slave` - Modbus serial slave ASCII/RTU
|
||||
|
||||
See README.md for each individual project for more information.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be run on any commonly available ESP32 development board.
|
||||
The master and slave boards should be connected to each other through the RS485 interface line driver.
|
||||
See the connection schematic in README.md files of each example.
|
||||
|
||||
### Configure the project
|
||||
|
||||
This example test requires communication mode setting for master and slave be the same and slave address set to 1.
|
||||
Please refer to README.md files of each example project for more information.
|
||||
|
||||
## About common_component in this example
|
||||
|
||||
The folder "mb_example_common" includes definitions of parameter structures for master and slave device (both projects share the same parameters).
|
||||
However, currently it is for example purpose only and can be modified for particular application.
|
||||
|
||||
## Example Output
|
||||
|
||||
Example of Slave output:
|
||||
|
||||
```
|
||||
I (343) SLAVE_TEST: Modbus slave stack initialized.
|
||||
I (343) SLAVE_TEST: Start modbus test...
|
||||
I (81463) SLAVE_TEST: HOLDING READ (81150420 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
|
||||
I (82463) SLAVE_TEST: HOLDING READ (82150720 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
|
||||
I (83573) SLAVE_TEST: HOLDING READ (83260630 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
|
||||
I (84603) SLAVE_TEST: HOLDING READ (84290530 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
|
||||
I (85703) SLAVE_TEST: HOLDING READ (85396692 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
|
||||
```
|
||||
|
||||
Example of Modbus Master output:
|
||||
|
||||
```
|
||||
I (399) MASTER_TEST: Modbus master stack initialized...
|
||||
I (499) MASTER_TEST: Start modbus test...
|
||||
I (549) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.230000 (0x3f9d70a4) read successful.
|
||||
I (629) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 12.100000 (0x4141999a) read successful.
|
||||
I (709) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (769) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 23.400000 (0x41bb3333) read successful.
|
||||
I (829) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 5.890000 (0x40bc7ae1) read successful.
|
||||
I (889) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 34.500000 (0x420a0000) read successful.
|
||||
E (949) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x108) (ESP_ERR_INVALID_RESPONSE).
|
||||
E (949) MASTER_TEST: Characteristic #6 (RelayP1) read fail, err = 264 (ESP_ERR_INVALID_RESPONSE).
|
||||
E (1029) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x108) (ESP_ERR_INVALID_RESPONSE).
|
||||
E (1029) MASTER_TEST: Characteristic #7 (RelayP2) read fail, err = 264 (ESP_ERR_INVALID_RESPONSE).
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the examples do not work as expected and slave and master boards are not able to communicate correctly it is possible to find the reason for errors.
|
||||
The most important errors are described in master example output and formatted as below:
|
||||
|
||||
```
|
||||
E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT).
|
||||
```
|
||||
|
||||
ESP_ERR_TIMEOUT (0x107) - Modbus slave device does not respond during configured timeout. Check the connection and ability for communication using uart_echo_rs485 example or increase
|
||||
Kconfig value CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND (CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS).
|
||||
|
||||
ESP_ERR_NOT_SUPPORTED (0x106), ESP_ERR_INVALID_RESPONSE (0x108) - Modbus slave device does not support requested command or register and sent exeption response.
|
||||
|
||||
ESP_ERR_INVALID_STATE (0x103) - Modbus stack is not configured correctly or can't work correctly due to critical failure.
|
||||
|
||||
|
@ -1,290 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Need Python 3 string formatting functions
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from threading import Thread
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = 'modbus_test'
|
||||
|
||||
# Allowed parameter reads
|
||||
TEST_READ_MIN_COUNT = 10 # Minimum number of correct readings
|
||||
TEST_READ_MAX_ERR_COUNT = 2 # Maximum allowed read errors during initialization
|
||||
|
||||
TEST_THREAD_EXPECT_TIMEOUT = 120 # Test theread expect timeout in seconds
|
||||
TEST_THREAD_JOIN_TIMEOUT = 180 # Test theread join timeout in seconds
|
||||
|
||||
# Test definitions
|
||||
TEST_MASTER_RTU = 'master_rtu'
|
||||
TEST_SLAVE_RTU = 'slave_rtu'
|
||||
|
||||
TEST_MASTER_ASCII = 'master_ascii'
|
||||
TEST_SLAVE_ASCII = 'slave_ascii'
|
||||
|
||||
# Define tuple of strings to expect for each DUT.
|
||||
#
|
||||
master_expect = ('MASTER_TEST: Modbus master stack initialized...', 'MASTER_TEST: Start modbus test...', 'MASTER_TEST: Destroy master...')
|
||||
slave_expect = ('SLAVE_TEST: Modbus slave stack initialized.', 'SLAVE_TEST: Start modbus test...', 'SLAVE_TEST: Modbus controller destroyed.')
|
||||
|
||||
# The dictionary for expected values in listing
|
||||
expect_dict_master_ok = {'START': (),
|
||||
'READ_PAR_OK': (),
|
||||
'ALARM_MSG': (u'7',)}
|
||||
|
||||
expect_dict_master_err = {'READ_PAR_ERR': (u'263', u'ESP_ERR_TIMEOUT'),
|
||||
'READ_STK_ERR': (u'107', u'ESP_ERR_TIMEOUT')}
|
||||
|
||||
# The dictionary for regular expression patterns to check in listing
|
||||
pattern_dict_master_ok = {'START': (r'.*I \([0-9]+\) MASTER_TEST: Start modbus test...'),
|
||||
'READ_PAR_OK': (r'.*I\s\([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]+\) read successful.'),
|
||||
'ALARM_MSG': (r'.*I \([0-9]*\) MASTER_TEST: Alarm triggered by cid #([0-9]+).')}
|
||||
|
||||
pattern_dict_master_err = {'READ_PAR_ERR_TOUT': (r'.*E \([0-9]+\) MASTER_TEST: Characteristic #[0-9]+'
|
||||
r'\s\([a-zA-Z0-9_]+\) read fail, err = [0-9]+ \([_A-Z]+\).'),
|
||||
'READ_STK_ERR_TOUT': (r'.*E \([0-9]+\) MB_CONTROLLER_MASTER: [a-zA-Z0-9_]+\([0-9]+\):\s'
|
||||
r'SERIAL master get parameter failure error=\(0x([a-zA-Z0-9]+)\) \(([_A-Z]+)\).')}
|
||||
|
||||
# The dictionary for expected values in listing
|
||||
expect_dict_slave_ok = {'START': (),
|
||||
'READ_PAR_OK': (),
|
||||
'DESTROY': ()}
|
||||
|
||||
# The dictionary for regular expression patterns to check in listing
|
||||
pattern_dict_slave_ok = {'START': (r'.*I \([0-9]+\) SLAVE_TEST: Start modbus test...'),
|
||||
'READ_PAR_OK': (r'.*I\s\([0-9]+\) SLAVE_TEST: [A-Z]+ READ \([a-zA-Z0-9_]+ us\),\s'
|
||||
r'ADDR:[0-9]+, TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'),
|
||||
'DESTROY': (r'.*I\s\([0-9]+\) SLAVE_TEST: Modbus controller destroyed.')}
|
||||
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
|
||||
class DutTestThread(Thread):
|
||||
def __init__(self, dut=None, name=None, expect=None):
|
||||
""" Initialize the thread parameters
|
||||
"""
|
||||
self.tname = name
|
||||
self.dut = dut
|
||||
self.expected = expect
|
||||
self.result = False
|
||||
self.data = None
|
||||
super(DutTestThread, self).__init__()
|
||||
|
||||
def run(self):
|
||||
""" The function implements thread functionality
|
||||
"""
|
||||
# Must reset again as flashing during start_app will reset multiple times, causing unexpected results
|
||||
self.dut.reset()
|
||||
|
||||
# Capture output from the DUT
|
||||
self.dut.start_capture_raw_data()
|
||||
|
||||
# Check expected strings in the listing
|
||||
for string in self.expected:
|
||||
self.dut.expect(string, TEST_THREAD_EXPECT_TIMEOUT)
|
||||
|
||||
# Check DUT exceptions
|
||||
dut_exceptions = self.dut.get_exceptions()
|
||||
if 'Guru Meditation Error:' in dut_exceptions:
|
||||
raise Exception('%s generated an exception: %s\n' % (str(self.dut), dut_exceptions))
|
||||
|
||||
# Mark thread has run to completion without any exceptions
|
||||
self.data = self.dut.stop_capture_raw_data()
|
||||
self.result = True
|
||||
|
||||
|
||||
def test_filter_output(data=None, start_pattern=None, end_pattern=None):
|
||||
"""Use patters to filter output
|
||||
"""
|
||||
start_index = str(data).find(start_pattern)
|
||||
end_index = str(data).find(end_pattern)
|
||||
logger.debug('Listing start index= %d, end=%d' % (start_index, end_index))
|
||||
if start_index == -1 or end_index == -1:
|
||||
return data
|
||||
return data[start_index:end_index + len(end_pattern)]
|
||||
|
||||
|
||||
def test_expect_re(data, pattern):
|
||||
"""
|
||||
Check if re pattern is matched in data cache
|
||||
:param data: data to process
|
||||
:param pattern: compiled RegEx pattern
|
||||
:return: match groups if match succeed otherwise None
|
||||
"""
|
||||
ret = None
|
||||
if isinstance(pattern, type(u'')):
|
||||
pattern = pattern.encode('utf-8')
|
||||
regex = re.compile(pattern)
|
||||
if isinstance(data, type(u'')):
|
||||
data = data.encode('utf-8')
|
||||
match = regex.search(data)
|
||||
if match:
|
||||
ret = tuple(None if x is None else x.decode() for x in match.groups())
|
||||
index = match.end()
|
||||
else:
|
||||
index = None
|
||||
return ret, index
|
||||
|
||||
|
||||
def test_check_output(data=None, check_dict=None, expect_dict=None):
|
||||
""" Check output for the test
|
||||
Check log using regular expressions:
|
||||
"""
|
||||
global logger
|
||||
match_count = 0
|
||||
index = 0
|
||||
data_lines = data.splitlines()
|
||||
for key, pattern in check_dict.items():
|
||||
if key not in expect_dict:
|
||||
break
|
||||
# Check pattern in the each line
|
||||
for line in data_lines:
|
||||
group, index = test_expect_re(line, pattern)
|
||||
if index is not None:
|
||||
logger.debug('Found key{%s}=%s, line: \n%s' % (key, group, line))
|
||||
if expect_dict[key] == group:
|
||||
logger.debug('The result is correct for the key:%s, expected:%s == returned:%s' % (key, str(expect_dict[key]), str(group)))
|
||||
match_count += 1
|
||||
return match_count
|
||||
|
||||
|
||||
def test_check_mode(dut=None, mode_str=None, value=None):
|
||||
""" Check communication mode for dut
|
||||
"""
|
||||
global logger
|
||||
try:
|
||||
opt = dut.app.get_sdkconfig()[mode_str]
|
||||
logger.info('%s {%s} = %s.\n' % (str(dut), mode_str, opt))
|
||||
return value == opt
|
||||
except Exception:
|
||||
logger.info('ENV_TEST_FAILURE: %s: Cannot find option %s in sdkconfig.' % (str(dut), mode_str))
|
||||
return False
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_T2_RS485', target=['esp32'])
|
||||
def test_modbus_serial_communication(env, comm_mode):
|
||||
global logger
|
||||
|
||||
# Get device under test. "dut1 - master", "dut2 - slave" must be properly connected through RS485 interface driver
|
||||
dut_master = env.get_dut('modbus_master', 'examples/protocols/modbus/serial/mb_master', dut_class=ttfw_idf.ESP32DUT)
|
||||
dut_slave = env.get_dut('modbus_slave', 'examples/protocols/modbus/serial/mb_slave', dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
try:
|
||||
logger.debug('Environment vars: %s\r\n' % os.environ)
|
||||
logger.debug('DUT slave sdkconfig: %s\r\n' % dut_slave.app.get_sdkconfig())
|
||||
logger.debug('DUT master sdkconfig: %s\r\n' % dut_master.app.get_sdkconfig())
|
||||
|
||||
# Check Kconfig configuration options for each built example
|
||||
if test_check_mode(dut_master, 'CONFIG_MB_COMM_MODE_ASCII', 'y') and test_check_mode(dut_slave, 'CONFIG_MB_COMM_MODE_ASCII', 'y'):
|
||||
logger.info('ENV_TEST_INFO: Modbus ASCII test mode selected in the configuration. \n')
|
||||
slave_name = TEST_SLAVE_ASCII
|
||||
master_name = TEST_MASTER_ASCII
|
||||
elif test_check_mode(dut_master, 'CONFIG_MB_COMM_MODE_RTU', 'y') and test_check_mode(dut_slave, 'CONFIG_MB_COMM_MODE_RTU', 'y'):
|
||||
logger.info('ENV_TEST_INFO: Modbus RTU test mode selected in the configuration. \n')
|
||||
slave_name = TEST_SLAVE_RTU
|
||||
master_name = TEST_MASTER_RTU
|
||||
else:
|
||||
logger.error("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n")
|
||||
raise Exception("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n")
|
||||
# Check if slave address for example application is default one to be able to communicate
|
||||
if not test_check_mode(dut_slave, 'CONFIG_MB_SLAVE_ADDR', '1'):
|
||||
logger.error('ENV_TEST_FAILURE: Slave address option is incorrect.\n')
|
||||
raise Exception('ENV_TEST_FAILURE: Slave address option is incorrect.\n')
|
||||
|
||||
# Flash app onto each DUT
|
||||
dut_master.start_app()
|
||||
dut_slave.start_app()
|
||||
|
||||
# Create thread for each dut
|
||||
dut_master_thread = DutTestThread(dut=dut_master, name=master_name, expect=master_expect)
|
||||
dut_slave_thread = DutTestThread(dut=dut_slave, name=slave_name, expect=slave_expect)
|
||||
|
||||
# Start each thread
|
||||
dut_slave_thread.start()
|
||||
dut_master_thread.start()
|
||||
|
||||
# Wait for threads to complete
|
||||
dut_slave_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
dut_master_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
|
||||
if dut_slave_thread.isAlive():
|
||||
logger.error('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
if dut_master_thread.isAlive():
|
||||
logger.error('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
finally:
|
||||
dut_master.close()
|
||||
dut_slave.close()
|
||||
|
||||
# Check if test threads completed successfully and captured data
|
||||
if not dut_slave_thread.result or dut_slave_thread.data is None:
|
||||
logger.error('The thread %s was not run successfully.' % dut_slave_thread.tname)
|
||||
raise Exception('The thread %s was not run successfully.' % dut_slave_thread.tname)
|
||||
|
||||
if not dut_master_thread.result or dut_master_thread.data is None:
|
||||
logger.error('The thread %s was not run successfully.' % dut_slave_thread.tname)
|
||||
raise Exception('The thread %s was not run successfully.' % dut_master_thread.tname)
|
||||
|
||||
# Filter output to get test messages
|
||||
master_output = test_filter_output(dut_master_thread.data, master_expect[0], master_expect[len(master_expect) - 1])
|
||||
if master_output is not None:
|
||||
logger.info('The data for master thread is captured.')
|
||||
logger.debug(master_output)
|
||||
|
||||
slave_output = test_filter_output(dut_slave_thread.data, slave_expect[0], slave_expect[len(slave_expect) - 1])
|
||||
if slave_output is not None:
|
||||
logger.info('The data for slave thread is captured.')
|
||||
logger.debug(slave_output)
|
||||
|
||||
# Check if parameters are read correctly by master
|
||||
match_count = test_check_output(master_output, pattern_dict_master_ok, expect_dict_master_ok)
|
||||
if match_count < TEST_READ_MIN_COUNT:
|
||||
logger.error('There are errors reading parameters from %s, %d' % (dut_master_thread.tname, match_count))
|
||||
raise Exception('There are errors reading parameters from %s, %d' % (dut_master_thread.tname, match_count))
|
||||
logger.info('OK pattern test for %s, match_count=%d.' % (dut_master_thread.tname, match_count))
|
||||
|
||||
# If the test completed successfully (alarm triggered) but there are some errors during reading of parameters
|
||||
match_count = test_check_output(master_output, pattern_dict_master_err, expect_dict_master_err)
|
||||
if match_count > TEST_READ_MAX_ERR_COUNT:
|
||||
logger.error('There are errors reading parameters from %s, %d' % (dut_master_thread.tname, match_count))
|
||||
raise Exception('There are errors reading parameters from %s, %d' % (dut_master_thread.tname, match_count))
|
||||
logger.info('ERROR pattern test for %s, match_count=%d.' % (dut_master_thread.tname, match_count))
|
||||
|
||||
match_count = test_check_output(slave_output, pattern_dict_slave_ok, expect_dict_slave_ok)
|
||||
if match_count < TEST_READ_MIN_COUNT:
|
||||
logger.error('There are errors reading parameters from %s, %d' % (dut_slave_thread.tname, match_count))
|
||||
raise Exception('There are errors reading parameters from %s, %d' % (dut_slave_thread.tname, match_count))
|
||||
logger.info('OK pattern test for %s, match_count=%d.' % (dut_slave_thread.tname, match_count))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
# create file handler which logs even debug messages
|
||||
fh = logging.FileHandler('modbus_test.log')
|
||||
fh.setLevel(logging.DEBUG)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# create console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
logger.addHandler(ch)
|
||||
logger.info('Start script %s.' % os.path.basename(__file__))
|
||||
test_modbus_serial_communication()
|
||||
logging.shutdown()
|
@ -1,8 +0,0 @@
|
||||
# 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_master)
|
@ -1,9 +0,0 @@
|
||||
# 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(modbus_slave)
|
@ -1,58 +0,0 @@
|
||||
# Modbus TCP Master-Slave Example
|
||||
|
||||
## Overview
|
||||
|
||||
These two projects illustrate the communication between Modbus master and slave device in the segment.
|
||||
Master initializes Modbus interface driver and then reads parameters from slave device in the segment.
|
||||
After several successful read attempts slave sets the alarm relay (end of test condition).
|
||||
Once master reads the alarm it stops communication and destroy driver.
|
||||
|
||||
The examples:
|
||||
|
||||
* `examples/protocols/modbus/tcp/mb_tcp_master` - Modbus TCP master
|
||||
* `examples/protocols/modbus/tcp/mb_tcp_slave` - Modbus TCP slave
|
||||
|
||||
See README.md for each individual project for more information.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be run on any commonly available ESP32(-S2) development board.
|
||||
The master and slave boards should be connected to the same network (see the README.md file in example folder) and slave address `CONFIG_MB_SLAVE_ADDR` be defined for slave board(s).
|
||||
See the connection schematic in README.md files of each example.
|
||||
|
||||
### Configure the project
|
||||
|
||||
This example test requires communication mode setting for master and slave be the same and slave address set to 1.
|
||||
Please refer to README.md files of each example project for more information. This example uses the default option `CONFIG_MB_SLAVE_IP_FROM_STDIN` to resolve slave IP address and supports IPv4 address type for communication in this case.
|
||||
|
||||
## About common_component in this example
|
||||
|
||||
The folder "mb_example_common" one level above includes definitions of parameter structures for master and slave device (both projects share the same parameters).
|
||||
However, currently it is for example purpose only and can be modified for particular application.
|
||||
|
||||
## Example Output
|
||||
|
||||
Refer to README.md file in the appropriate example folder for more information about master and slave log output.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the examples do not work as expected and slave and master boards are not able to communicate correctly it is possible to find the reason for errors.
|
||||
The most important errors are described in master example output and formatted as below:
|
||||
|
||||
```
|
||||
E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT).
|
||||
```
|
||||
|
||||
ESP_ERR_TIMEOUT (0x107) - Modbus slave device does not respond during configured timeout.
|
||||
Check ability for communication pinging each slave configured in the master parameter description table or use command on your host machine to find modbus slave using mDNS (requires `CONFIG_MB_MDNS_IP_RESOLVER` option be enabled):
|
||||
```>dns-sd -L mb_slave_tcp_XX _modbus._tcp .```
|
||||
where XX is the short slave address (index) of the slave configured in the Kconfig of slave example.
|
||||
Also it is possible to increase Kconfig value `CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND` to compensate network communication delays between master and slaves.
|
||||
|
||||
ESP_ERR_NOT_SUPPORTED (0x106), ESP_ERR_INVALID_RESPONSE (0x108) - Modbus slave device does not support requested command or register and sent exeption response.
|
||||
|
||||
ESP_ERR_INVALID_STATE (0x103) - Modbus stack is not configured correctly or can't work correctly due to critical failure.
|
||||
|
||||
|
@ -1,308 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from threading import Thread
|
||||
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import DUT
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = 'modbus_test'
|
||||
|
||||
# Allowed options for the test
|
||||
TEST_READ_MAX_ERR_COUNT = 3 # Maximum allowed read errors during initialization
|
||||
TEST_THREAD_JOIN_TIMEOUT = 60 # Test theread join timeout in seconds
|
||||
TEST_EXPECT_STR_TIMEOUT = 30 # Test expect timeout in seconds
|
||||
TEST_MASTER_TCP = 'mb_tcp_master'
|
||||
TEST_SLAVE_TCP = 'mb_tcp_slave'
|
||||
|
||||
STACK_DEFAULT = 0
|
||||
STACK_IPV4 = 1
|
||||
STACK_IPV6 = 2
|
||||
STACK_INIT = 3
|
||||
STACK_CONNECT = 4
|
||||
STACK_START = 5
|
||||
STACK_PAR_OK = 6
|
||||
STACK_PAR_FAIL = 7
|
||||
STACK_DESTROY = 8
|
||||
|
||||
pattern_dict_slave = {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}).*'),
|
||||
STACK_IPV6: (r'.*I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}).*'),
|
||||
STACK_INIT: (r'.*I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
|
||||
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}).*'),
|
||||
STACK_START: (r'.*I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test).*'),
|
||||
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]+'),
|
||||
STACK_PAR_FAIL: (r'.*E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet.*'),
|
||||
STACK_DESTROY: (r'.*I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed).')}
|
||||
|
||||
pattern_dict_master = {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}).*'),
|
||||
STACK_IPV6: (r'.*I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}).*'),
|
||||
STACK_INIT: (r'.*I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized).*'),
|
||||
STACK_CONNECT: (r'.*.*I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling.*'),
|
||||
STACK_START: (r'.*I \(([0-9]+)\) MASTER_TEST: (Start modbus test).*'),
|
||||
STACK_PAR_OK: (r'.*I\s\(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)'
|
||||
r'\s\([a-zA-Z\%\/]+\) value = [a-zA-Z0-9\.]+ \(0x[a-zA-Z0-9]+\) read successful.*'),
|
||||
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]+\).*'),
|
||||
STACK_DESTROY: (r'.*I\s\(([0-9]+)\) MASTER_TEST: (Destroy master).*')}
|
||||
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
|
||||
class DutTestThread(Thread):
|
||||
""" Test thread class
|
||||
"""
|
||||
def __init__(self, dut=None, name=None, ip_addr=None, expect=None):
|
||||
""" Initialize the thread parameters
|
||||
"""
|
||||
self.tname = name
|
||||
self.dut = dut
|
||||
self.expected = expect
|
||||
self.data = None
|
||||
self.ip_addr = ip_addr
|
||||
self.test_finish = False
|
||||
self.param_fail_count = 0
|
||||
self.param_ok_count = 0
|
||||
self.test_stage = STACK_DEFAULT
|
||||
super(DutTestThread, self).__init__()
|
||||
|
||||
def __enter__(self):
|
||||
logger.debug('Restart %s.', self.tname)
|
||||
# Reset DUT first
|
||||
self.dut.reset()
|
||||
# Capture output from the DUT
|
||||
self.dut.start_capture_raw_data(capture_id=self.dut.name)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
""" The exit method of context manager
|
||||
"""
|
||||
if exc_type is not None or exc_value is not None:
|
||||
logger.info('Thread %s rised an exception type: %s, value: %s', self.tname, str(exc_type), str(exc_value))
|
||||
|
||||
def run(self):
|
||||
""" The function implements thread functionality
|
||||
"""
|
||||
# Initialize slave IP for master board
|
||||
if (self.ip_addr is not None):
|
||||
self.set_ip(0)
|
||||
|
||||
# Check expected strings in the listing
|
||||
self.test_start(TEST_EXPECT_STR_TIMEOUT)
|
||||
|
||||
# Check DUT exceptions
|
||||
dut_exceptions = self.dut.get_exceptions()
|
||||
if 'Guru Meditation Error:' in dut_exceptions:
|
||||
raise RuntimeError('%s generated an exception(s): %s\n' % (str(self.dut), dut_exceptions))
|
||||
|
||||
# Mark thread has run to completion without any exceptions
|
||||
self.data = self.dut.stop_capture_raw_data(capture_id=self.dut.name)
|
||||
|
||||
def set_ip(self, index=0):
|
||||
""" The method to send slave IP to master application
|
||||
"""
|
||||
message = r'.*Waiting IP([0-9]{1,2}) from stdin.*'
|
||||
# Read all data from previous restart to get prompt correctly
|
||||
self.dut.read()
|
||||
result = self.dut.expect(re.compile(message), timeout=TEST_EXPECT_STR_TIMEOUT)
|
||||
if int(result[0]) != index:
|
||||
raise RuntimeError('Incorrect index of IP=%s for %s\n' % (result[0], str(self.dut)))
|
||||
# Use the same slave IP address for all characteristics during the test
|
||||
self.dut.write('IP0=' + self.ip_addr, '\n', False)
|
||||
self.dut.write('IP1=' + self.ip_addr, '\n', False)
|
||||
self.dut.write('IP2=' + self.ip_addr, '\n', False)
|
||||
logger.debug('Set IP address=%s for %s', self.ip_addr, self.tname)
|
||||
message = r'.*IP\([0-9]+\) = \[([0-9a-zA-Z\.\:]+)\] set from stdin.*'
|
||||
result = self.dut.expect(re.compile(message), timeout=TEST_EXPECT_STR_TIMEOUT)
|
||||
logger.debug('Thread %s initialized with slave IP=%s.', self.tname, self.ip_addr)
|
||||
|
||||
def test_start(self, timeout_value):
|
||||
""" The method to initialize and handle test stages
|
||||
"""
|
||||
def handle_get_ip4(data):
|
||||
""" Handle get_ip v4
|
||||
"""
|
||||
logger.debug('%s[STACK_IPV4]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_IPV4
|
||||
|
||||
def handle_get_ip6(data):
|
||||
""" Handle get_ip v6
|
||||
"""
|
||||
logger.debug('%s[STACK_IPV6]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_IPV6
|
||||
|
||||
def handle_init(data):
|
||||
""" Handle init
|
||||
"""
|
||||
logger.debug('%s[STACK_INIT]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_INIT
|
||||
|
||||
def handle_connect(data):
|
||||
""" Handle connect
|
||||
"""
|
||||
logger.debug('%s[STACK_CONNECT]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_CONNECT
|
||||
|
||||
def handle_test_start(data):
|
||||
""" Handle connect
|
||||
"""
|
||||
logger.debug('%s[STACK_START]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_START
|
||||
|
||||
def handle_par_ok(data):
|
||||
""" Handle parameter ok
|
||||
"""
|
||||
logger.debug('%s[READ_PAR_OK]: %s', self.tname, str(data))
|
||||
if self.test_stage >= STACK_START:
|
||||
self.param_ok_count += 1
|
||||
self.test_stage = STACK_PAR_OK
|
||||
|
||||
def handle_par_fail(data):
|
||||
""" Handle parameter fail
|
||||
"""
|
||||
logger.debug('%s[READ_PAR_FAIL]: %s', self.tname, str(data))
|
||||
self.param_fail_count += 1
|
||||
self.test_stage = STACK_PAR_FAIL
|
||||
|
||||
def handle_destroy(data):
|
||||
""" Handle destroy
|
||||
"""
|
||||
logger.debug('%s[DESTROY]: %s', self.tname, str(data))
|
||||
self.test_stage = STACK_DESTROY
|
||||
self.test_finish = True
|
||||
|
||||
while not self.test_finish:
|
||||
try:
|
||||
self.dut.expect_any((re.compile(self.expected[STACK_IPV4]), handle_get_ip4),
|
||||
(re.compile(self.expected[STACK_IPV6]), handle_get_ip6),
|
||||
(re.compile(self.expected[STACK_INIT]), handle_init),
|
||||
(re.compile(self.expected[STACK_CONNECT]), handle_connect),
|
||||
(re.compile(self.expected[STACK_START]), handle_test_start),
|
||||
(re.compile(self.expected[STACK_PAR_OK]), handle_par_ok),
|
||||
(re.compile(self.expected[STACK_PAR_FAIL]), handle_par_fail),
|
||||
(re.compile(self.expected[STACK_DESTROY]), handle_destroy),
|
||||
timeout=timeout_value)
|
||||
except DUT.ExpectTimeout:
|
||||
logger.debug('%s, expect timeout on stage #%d (%s seconds)', self.tname, self.test_stage, timeout_value)
|
||||
self.test_finish = True
|
||||
|
||||
|
||||
def test_check_mode(dut=None, mode_str=None, value=None):
|
||||
""" Check communication mode for dut
|
||||
"""
|
||||
global logger
|
||||
try:
|
||||
opt = dut.app.get_sdkconfig()[mode_str]
|
||||
logger.debug('%s {%s} = %s.\n', str(dut), mode_str, opt)
|
||||
return value == opt
|
||||
except Exception:
|
||||
logger.error('ENV_TEST_FAILURE: %s: Cannot find option %s in sdkconfig.', str(dut), mode_str)
|
||||
return False
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_Modbus_TCP', target=['esp32'])
|
||||
def test_modbus_tcp_communication(env, comm_mode):
|
||||
global logger
|
||||
|
||||
rel_project_path = os.path.join('examples', 'protocols', 'modbus', 'tcp')
|
||||
# Get device under test. Both duts must be able to be connected to WiFi router
|
||||
dut_master = env.get_dut('modbus_tcp_master', os.path.join(rel_project_path, TEST_MASTER_TCP))
|
||||
dut_slave = env.get_dut('modbus_tcp_slave', os.path.join(rel_project_path, TEST_SLAVE_TCP))
|
||||
log_file = os.path.join(env.log_path, 'modbus_tcp_test.log')
|
||||
print('Logging file name: %s' % log_file)
|
||||
|
||||
try:
|
||||
# create file handler which logs even debug messages
|
||||
logger.setLevel(logging.DEBUG)
|
||||
fh = logging.FileHandler(log_file)
|
||||
fh.setLevel(logging.DEBUG)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
# create console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
# Check Kconfig configuration options for each built example
|
||||
if (test_check_mode(dut_master, 'CONFIG_FMB_COMM_MODE_TCP_EN', 'y') and
|
||||
test_check_mode(dut_slave, 'CONFIG_FMB_COMM_MODE_TCP_EN', 'y')):
|
||||
slave_name = TEST_SLAVE_TCP
|
||||
master_name = TEST_MASTER_TCP
|
||||
else:
|
||||
logger.error('ENV_TEST_FAILURE: IP resolver mode do not match in the master and slave implementation.\n')
|
||||
raise RuntimeError('ENV_TEST_FAILURE: IP resolver mode do not match in the master and slave implementation.\n')
|
||||
address = None
|
||||
if test_check_mode(dut_master, 'CONFIG_MB_SLAVE_IP_FROM_STDIN', 'y'):
|
||||
logger.info('ENV_TEST_INFO: Set slave IP address through STDIN.\n')
|
||||
# Flash app onto DUT (Todo: Debug case when the slave flashed before master then expect does not work correctly for no reason
|
||||
dut_slave.start_app()
|
||||
dut_master.start_app()
|
||||
if test_check_mode(dut_master, 'CONFIG_EXAMPLE_CONNECT_IPV6', 'y'):
|
||||
address = dut_slave.expect(re.compile(pattern_dict_slave[STACK_IPV6]), TEST_EXPECT_STR_TIMEOUT)
|
||||
else:
|
||||
address = dut_slave.expect(re.compile(pattern_dict_slave[STACK_IPV4]), TEST_EXPECT_STR_TIMEOUT)
|
||||
if address is not None:
|
||||
print('Found IP slave address: %s' % address[0])
|
||||
else:
|
||||
raise RuntimeError('ENV_TEST_FAILURE: Slave IP address is not found in the output. Check network settings.\n')
|
||||
else:
|
||||
raise RuntimeError('ENV_TEST_FAILURE: Slave IP resolver is not configured correctly.\n')
|
||||
|
||||
# Create thread for each dut
|
||||
with DutTestThread(dut=dut_master, name=master_name, ip_addr=address[0], expect=pattern_dict_master) as dut_master_thread:
|
||||
with DutTestThread(dut=dut_slave, name=slave_name, ip_addr=None, expect=pattern_dict_slave) as dut_slave_thread:
|
||||
|
||||
# Start each thread
|
||||
dut_slave_thread.start()
|
||||
dut_master_thread.start()
|
||||
|
||||
# Wait for threads to complete
|
||||
dut_slave_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
dut_master_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
|
||||
if dut_slave_thread.is_alive():
|
||||
logger.error('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n',
|
||||
dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT)
|
||||
raise RuntimeError('ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
if dut_master_thread.is_alive():
|
||||
logger.error('TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n',
|
||||
dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT)
|
||||
raise RuntimeError('TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n' %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
logger.info('TEST_INFO: %s error count = %d, %s error count = %d.\n',
|
||||
dut_master_thread.tname, dut_master_thread.param_fail_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_fail_count)
|
||||
logger.info('TEST_INFO: %s ok count = %d, %s ok count = %d.\n',
|
||||
dut_master_thread.tname, dut_master_thread.param_ok_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_ok_count)
|
||||
|
||||
if ((dut_master_thread.param_fail_count > TEST_READ_MAX_ERR_COUNT) or
|
||||
(dut_slave_thread.param_fail_count > TEST_READ_MAX_ERR_COUNT) or
|
||||
(dut_slave_thread.param_ok_count == 0) or
|
||||
(dut_master_thread.param_ok_count == 0)):
|
||||
raise RuntimeError('TEST_FAILURE: %s parameter read error(ok) count = %d(%d), %s parameter read error(ok) count = %d(%d).\n' %
|
||||
(dut_master_thread.tname, dut_master_thread.param_fail_count, dut_master_thread.param_ok_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_fail_count, dut_slave_thread.param_ok_count))
|
||||
logger.info('TEST_SUCCESS: The Modbus parameter test is completed successfully.\n')
|
||||
|
||||
finally:
|
||||
dut_master.close()
|
||||
dut_slave.close()
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_modbus_tcp_communication()
|
@ -15,6 +15,7 @@ extern "C" {
|
||||
|
||||
#if __has_include("esp_check.h")
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) ESP_RETURN_ON_FALSE(a, err_code, tag, format __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
@ -24,7 +25,7 @@ extern "C" {
|
||||
|
||||
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) do { \
|
||||
if (!(a)) { \
|
||||
MB_LOGE(tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
|
||||
ESP_LOGE(tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
|
||||
return err_code; \
|
||||
} \
|
||||
} while(0)
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "soc/soc.h" // for BITN definitions
|
||||
#include "esp_modbus_common.h" // for common types
|
||||
|
||||
#ifdef __cplusplus
|
@ -10,6 +10,7 @@
|
||||
// Public interface header for slave
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "soc/soc.h" // for BITN definitions
|
||||
#include "freertos/FreeRTOS.h" // for task creation and queues access
|
||||
#include "freertos/event_groups.h" // for event groups
|
||||
#include "esp_modbus_common.h" // for common types
|
@ -151,26 +151,33 @@ eMBErrorCode
|
||||
eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
|
||||
{
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
UCHAR *pucMBASCIIFrame = ( UCHAR* ) ucASCIIBuf;
|
||||
USHORT usFrameLength = usRcvBufferPos;
|
||||
|
||||
if( xMBSerialPortGetRequest( &pucMBASCIIFrame, &usFrameLength ) == FALSE )
|
||||
{
|
||||
return MB_EIO;
|
||||
}
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
|
||||
assert( usFrameLength < MB_SER_PDU_SIZE_MAX );
|
||||
|
||||
/* Length and CRC check */
|
||||
if( ( usRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN )
|
||||
&& ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
|
||||
if( ( usFrameLength >= MB_ASCII_SER_PDU_SIZE_MIN )
|
||||
&& ( prvucMBLRC( ( UCHAR * ) pucMBASCIIFrame, usFrameLength ) == 0 ) )
|
||||
{
|
||||
/* Save the address field. All frames are passed to the upper layed
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
|
||||
*pucRcvAddress = pucMBASCIIFrame[MB_SER_PDU_ADDR_OFF];
|
||||
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
|
||||
*pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
|
||||
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
|
||||
*pucFrame = ( UCHAR * ) & pucMBASCIIFrame[MB_SER_PDU_PDU_OFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -186,13 +193,14 @@ eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
UCHAR usLRC;
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
|
||||
/* Check if the receiver is still in idle state. If not we where too
|
||||
* slow with processing the received frame and the master sent another
|
||||
* frame on the network. We have to abort sending the frame.
|
||||
*/
|
||||
if( eRcvState == STATE_RX_IDLE )
|
||||
{
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
/* First byte before the Modbus-PDU is the slave address. */
|
||||
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
|
||||
usSndBufferCount = 1;
|
||||
@ -207,6 +215,13 @@ eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
|
||||
|
||||
/* Activate the transmitter. */
|
||||
eSndState = STATE_TX_START;
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
|
||||
if ( xMBSerialPortSendResponse( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ) == FALSE )
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
|
||||
vMBPortSerialEnable( FALSE, TRUE );
|
||||
}
|
||||
else
|
@ -154,29 +154,34 @@ eMBErrorCode
|
||||
eMBMasterASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
|
||||
{
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
UCHAR *pucMBASCIIFrame = ( UCHAR* ) ucMasterASCIIRcvBuf;
|
||||
USHORT usFrameLength = usMasterRcvBufferPos;
|
||||
|
||||
if( xMBMasterSerialPortGetResponse( &pucMBASCIIFrame, &usFrameLength ) == FALSE )
|
||||
{
|
||||
return MB_EIO;
|
||||
}
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX );
|
||||
assert( usFrameLength < MB_SER_PDU_SIZE_MAX );
|
||||
|
||||
assert( pucMBASCIIFrame );
|
||||
/* Length and CRC check */
|
||||
if( ( usMasterRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN )
|
||||
&& ( prvucMBLRC( ( UCHAR * ) ucMasterASCIIRcvBuf, usMasterRcvBufferPos ) == 0 ) )
|
||||
if( ( usFrameLength >= MB_ASCII_SER_PDU_SIZE_MIN )
|
||||
&& ( prvucMBLRC( ( UCHAR * ) pucMBASCIIFrame, usFrameLength ) == 0 ) )
|
||||
{
|
||||
/* Save the address field. All frames are passed to the upper layed
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*pucRcvAddress = ucMasterASCIIRcvBuf[MB_SER_PDU_ADDR_OFF];
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*pucRcvAddress = pucMBASCIIFrame[MB_SER_PDU_ADDR_OFF];
|
||||
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
|
||||
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*pucFrame = ( UCHAR * ) & ucMasterASCIIRcvBuf[MB_SER_PDU_PDU_OFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*pucFrame = ( UCHAR * ) & pucMBASCIIFrame[MB_SER_PDU_PDU_OFF];
|
||||
} else {
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
@ -191,13 +196,13 @@ eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLengt
|
||||
|
||||
if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL;
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
/* Check if the receiver is still in idle state. If not we where too
|
||||
* slow with processing the received frame and the master sent another
|
||||
* frame on the network. We have to abort sending the frame.
|
||||
*/
|
||||
if(eRcvState == STATE_M_RX_IDLE)
|
||||
{
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
/* First byte before the Modbus-PDU is the slave address. */
|
||||
pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1;
|
||||
usMasterSndBufferCount = 1;
|
||||
@ -208,17 +213,22 @@ eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLengt
|
||||
|
||||
/* Calculate LRC checksum for Modbus-Serial-Line-PDU. */
|
||||
usLRC = prvucMBLRC( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount );
|
||||
ucMasterASCIISndBuf[usMasterSndBufferCount++] = usLRC;
|
||||
pucMasterSndBufferCur[usMasterSndBufferCount++] = usLRC;
|
||||
|
||||
/* Activate the transmitter. */
|
||||
eSndState = STATE_M_TX_START;
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
|
||||
if ( xMBMasterSerialPortSendRequest( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ) == FALSE )
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
vMBMasterPortSerialEnable( FALSE, TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
return eStatus;
|
||||
}
|
||||
|
||||
@ -447,7 +457,7 @@ xMBMasterASCIITransmitFSM( void )
|
||||
return xNeedPoll;
|
||||
}
|
||||
|
||||
BOOL
|
||||
BOOL MB_PORT_ISR_ATTR
|
||||
xMBMasterASCIITimerT1SExpired( void )
|
||||
{
|
||||
BOOL xNeedPoll = FALSE;
|
||||
@ -457,7 +467,6 @@ xMBMasterASCIITimerT1SExpired( void )
|
||||
/* Timer t35 expired. Startup phase is finished. */
|
||||
case STATE_M_RX_INIT:
|
||||
xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY);
|
||||
ESP_EARLY_LOGI("xMBMasterASCIITimerT1SExpired", "RX_INIT_EXPIRED");
|
||||
break;
|
||||
|
||||
/* Start of message is not received during respond timeout.
|
@ -41,6 +41,10 @@
|
||||
|
||||
#include "sdkconfig.h" // for KConfig options
|
||||
|
||||
#if __has_include("esp_idf_version.h")
|
||||
#include "esp_idf_version.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
PR_BEGIN_EXTERN_C
|
||||
#endif
|
||||
@ -73,6 +77,15 @@ PR_BEGIN_EXTERN_C
|
||||
#error "None of Modbus communication mode is enabled. Please enable one of (ASCII, RTU, TCP) mode in Kconfig."
|
||||
#endif
|
||||
|
||||
#ifdef ESP_IDF_VERSION
|
||||
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
|
||||
// Features supported from 4.4
|
||||
#define MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/*! \brief This option defines the number of data bits per ASCII character.
|
||||
*
|
||||
* A parity bit is added before the stop bit which keeps the actual byte size at 10 bits.
|
@ -145,6 +145,10 @@ BOOL xMBPortSerialGetByte( CHAR * pucByte );
|
||||
|
||||
BOOL xMBPortSerialPutByte( CHAR ucByte );
|
||||
|
||||
BOOL xMBSerialPortGetRequest( UCHAR **ppucMBSerialFrame, USHORT * pusSerialLength ) __attribute__ ((weak));
|
||||
|
||||
BOOL xMBSerialPortSendResponse( UCHAR *pucMBSerialFrame, USHORT usSerialLength ) __attribute__ ((weak));
|
||||
|
||||
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED
|
||||
BOOL xMBMasterPortSerialInit( UCHAR ucPort, ULONG ulBaudRate,
|
||||
UCHAR ucDataBits, eMBParity eParity );
|
||||
@ -158,6 +162,11 @@ void vMBMasterPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );
|
||||
BOOL xMBMasterPortSerialGetByte( CHAR * pucByte );
|
||||
|
||||
BOOL xMBMasterPortSerialPutByte( CHAR ucByte );
|
||||
|
||||
BOOL xMBMasterSerialPortGetResponse( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength );
|
||||
|
||||
BOOL xMBMasterSerialPortSendRequest( UCHAR *pucMBSerialFrame, USHORT usSerialLength );
|
||||
|
||||
#endif
|
||||
|
||||
/* ----------------------- Timers functions ---------------------------------*/
|
@ -70,18 +70,18 @@
|
||||
|
||||
/* ----------------------- Static variables ---------------------------------*/
|
||||
|
||||
static UCHAR ucMBMasterDestAddress;
|
||||
static BOOL xMBRunInMasterMode = FALSE;
|
||||
static UCHAR ucMBMasterDestAddress;
|
||||
static BOOL xMBRunInMasterMode = FALSE;
|
||||
static volatile eMBMasterErrorEventType eMBMasterCurErrorType;
|
||||
static volatile USHORT usMasterSendPDULength;
|
||||
static volatile USHORT usMasterSendPDULength;
|
||||
static volatile eMBMode eMBMasterCurrentMode;
|
||||
|
||||
/*------------------------ Shared variables ---------------------------------*/
|
||||
|
||||
volatile UCHAR ucMasterSndBuf[MB_SERIAL_BUF_SIZE];
|
||||
volatile UCHAR ucMasterRcvBuf[MB_SERIAL_BUF_SIZE];
|
||||
volatile UCHAR ucMasterSndBuf[MB_SERIAL_BUF_SIZE];
|
||||
volatile UCHAR ucMasterRcvBuf[MB_SERIAL_BUF_SIZE];
|
||||
volatile eMBMasterTimerMode eMasterCurTimerMode;
|
||||
volatile BOOL xFrameIsBroadcast = FALSE;
|
||||
volatile BOOL xFrameIsBroadcast = FALSE;
|
||||
|
||||
static enum
|
||||
{
|
||||
@ -152,7 +152,7 @@ static xMBFunctionHandler xMasterFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
|
||||
};
|
||||
|
||||
/* ----------------------- Start implementation -----------------------------*/
|
||||
#if MB_MASTER_TCP_ENABLED > 0
|
||||
#if MB_MASTER_TCP_ENABLED
|
||||
eMBErrorCode
|
||||
eMBMasterTCPInit( USHORT ucTCPPort )
|
||||
{
|
@ -39,7 +39,7 @@
|
||||
#include "port.h"
|
||||
#include "mbconfig.h"
|
||||
|
||||
#if MB_MASTER_RTU_ENABLED || MB_SLAVE_RTU_ENABLED
|
||||
#if (MB_MASTER_RTU_ENABLED || MB_SLAVE_RTU_ENABLED || CONFIG_MB_UTEST)
|
||||
|
||||
static const UCHAR aucCRCHi[] = {
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
@ -155,26 +155,33 @@ eMBErrorCode
|
||||
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
|
||||
{
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
UCHAR *pucMBRTUFrame = ( UCHAR* ) ucRTUBuf;
|
||||
USHORT usFrameLength = usRcvBufferPos;
|
||||
|
||||
if( xMBSerialPortGetRequest( &pucMBRTUFrame, &usFrameLength ) == FALSE )
|
||||
{
|
||||
return MB_EIO;
|
||||
}
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
|
||||
assert( usFrameLength < MB_SER_PDU_SIZE_MAX );
|
||||
|
||||
/* Length and CRC check */
|
||||
if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
|
||||
&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
|
||||
if( ( usFrameLength >= MB_SER_PDU_SIZE_MIN )
|
||||
&& ( usMBCRC16( ( UCHAR * ) pucMBRTUFrame, usFrameLength ) == 0 ) )
|
||||
{
|
||||
/* Save the address field. All frames are passed to the upper layed
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];
|
||||
*pucRcvAddress = pucMBRTUFrame[MB_SER_PDU_ADDR_OFF];
|
||||
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
|
||||
*pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
|
||||
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];
|
||||
*pucFrame = ( UCHAR * ) & pucMBRTUFrame[MB_SER_PDU_PDU_OFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -191,14 +198,13 @@ eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
USHORT usCRC16;
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
|
||||
/* Check if the receiver is still in idle state. If not we where to
|
||||
* slow with processing the received frame and the master sent another
|
||||
* frame on the network. We have to abort sending the frame.
|
||||
*/
|
||||
if( eRcvState == STATE_RX_IDLE )
|
||||
{
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
/* First byte before the Modbus-PDU is the slave address. */
|
||||
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
|
||||
usSndBufferCount = 1;
|
||||
@ -214,13 +220,19 @@ eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
|
||||
|
||||
/* Activate the transmitter. */
|
||||
eSndState = STATE_TX_XMIT;
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
|
||||
if( xMBSerialPortSendResponse( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ) == FALSE )
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
|
||||
vMBPortSerialEnable( FALSE, TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
return eStatus;
|
||||
}
|
||||
|
||||
@ -272,7 +284,7 @@ xMBRTUReceiveFSM( void )
|
||||
case STATE_RX_RCV:
|
||||
if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
|
||||
{
|
||||
if ( xStatus ) {
|
||||
if( xStatus ) {
|
||||
ucRTUBuf[usRcvBufferPos++] = ucByte;
|
||||
}
|
||||
}
|
@ -161,26 +161,34 @@ eMBErrorCode
|
||||
eMBMasterRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
|
||||
{
|
||||
eMBErrorCode eStatus = MB_ENOERR;
|
||||
UCHAR *pucMBRTUFrame = ( UCHAR* ) ucMasterRTURcvBuf;
|
||||
USHORT usFrameLength = usMasterRcvBufferPos;
|
||||
|
||||
if( xMBMasterSerialPortGetResponse( &pucMBRTUFrame, &usFrameLength ) == FALSE )
|
||||
{
|
||||
return MB_EIO;
|
||||
}
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX );
|
||||
assert( usFrameLength < MB_SER_PDU_SIZE_MAX );
|
||||
assert( pucMBRTUFrame );
|
||||
|
||||
/* Length and CRC check */
|
||||
if( ( usMasterRcvBufferPos >= MB_RTU_SER_PDU_SIZE_MIN )
|
||||
&& ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 ) )
|
||||
if( ( usFrameLength >= MB_RTU_SER_PDU_SIZE_MIN )
|
||||
&& ( usMBCRC16( ( UCHAR * ) pucMBRTUFrame, usFrameLength ) == 0 ) )
|
||||
{
|
||||
/* Save the address field. All frames are passed to the upper layed
|
||||
/* Save the address field. All frames are passed to the upper layer
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF];
|
||||
*pucRcvAddress = pucMBRTUFrame[MB_SER_PDU_ADDR_OFF];
|
||||
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
|
||||
*pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
|
||||
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*pucFrame = ( UCHAR * ) & ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF];
|
||||
*pucFrame = ( UCHAR * ) & pucMBRTUFrame[MB_SER_PDU_PDU_OFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -199,14 +207,13 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength
|
||||
|
||||
if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL;
|
||||
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
|
||||
/* Check if the receiver is still in idle state. If not we where to
|
||||
* slow with processing the received frame and the master sent another
|
||||
* frame on the network. We have to abort sending the frame.
|
||||
*/
|
||||
if( eRcvState == STATE_M_RX_IDLE )
|
||||
{
|
||||
ENTER_CRITICAL_SECTION( );
|
||||
/* First byte before the Modbus-PDU is the slave address. */
|
||||
pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1;
|
||||
usMasterSndBufferCount = 1;
|
||||
@ -217,11 +224,18 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength
|
||||
|
||||
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
|
||||
usCRC16 = usMBCRC16( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount );
|
||||
ucMasterRTUSndBuf[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
|
||||
ucMasterRTUSndBuf[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
|
||||
pucMasterSndBufferCur[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
|
||||
pucMasterSndBufferCur[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
|
||||
/* Activate the transmitter. */
|
||||
eSndState = STATE_M_TX_XMIT;
|
||||
|
||||
if ( xMBMasterSerialPortSendRequest( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ) == FALSE )
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
|
||||
// The place to enable RS485 driver
|
||||
vMBMasterPortSerialEnable( FALSE, TRUE );
|
||||
}
|
||||
@ -229,7 +243,6 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength
|
||||
{
|
||||
eStatus = MB_EIO;
|
||||
}
|
||||
EXIT_CRITICAL_SECTION( );
|
||||
return eStatus;
|
||||
}
|
||||
|
||||
@ -273,11 +286,15 @@ xMBMasterRTUReceiveFSM( void )
|
||||
eSndState = STATE_M_TX_IDLE;
|
||||
|
||||
usMasterRcvBufferPos = 0;
|
||||
ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
|
||||
eRcvState = STATE_M_RX_RCV;
|
||||
if( xStatus && ucByte ) {
|
||||
ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
|
||||
eRcvState = STATE_M_RX_RCV;
|
||||
}
|
||||
|
||||
/* Enable t3.5 timers. */
|
||||
#if CONFIG_FMB_TIMER_PORT_ENABLED
|
||||
vMBMasterPortTimersT35Enable( );
|
||||
#endif
|
||||
break;
|
||||
|
||||
/* We are currently receiving a frame. Reset the timer after
|
||||
@ -296,7 +313,9 @@ xMBMasterRTUReceiveFSM( void )
|
||||
{
|
||||
eRcvState = STATE_M_RX_ERROR;
|
||||
}
|
||||
#if CONFIG_FMB_TIMER_PORT_ENABLED
|
||||
vMBMasterPortTimersT35Enable( );
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return xStatus;
|
@ -38,6 +38,7 @@
|
||||
|
||||
/* ----------------------- Modbus includes ----------------------------------*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "sys/lock.h"
|
||||
#include "port.h"
|
||||
|
||||
@ -72,6 +73,13 @@ vMBPortSetMode( UCHAR ucMode )
|
||||
EXIT_CRITICAL_SECTION();
|
||||
}
|
||||
|
||||
BOOL xMBPortSerialWaitEvent(QueueHandle_t xMbUartQueue, uart_event_t* pxEvent, ULONG xTimeout)
|
||||
{
|
||||
BOOL xResult = (BaseType_t)xQueueReceive(xMbUartQueue, (void*)pxEvent, (TickType_t) xTimeout);
|
||||
ESP_LOGD(__func__, "UART event: %d ", pxEvent->type);
|
||||
return xResult;
|
||||
}
|
||||
|
||||
#if MB_TCP_DEBUG
|
||||
|
||||
// This function is kept to realize legacy freemodbus frame logging functionality
|
@ -38,8 +38,18 @@
|
||||
#define PORT_COMMON_H_
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h" // for queue
|
||||
|
||||
#include "esp_log.h" // for ESP_LOGE macro
|
||||
#include "esp_timer.h"
|
||||
#include "driver/uart.h" // for uart_event_t
|
||||
|
||||
#if __has_include("driver/gptimer.h")
|
||||
#include "driver/gptimer.h"
|
||||
#else
|
||||
#include "driver/timer.h"
|
||||
#endif
|
||||
|
||||
#include "mbconfig.h"
|
||||
|
||||
#define INLINE inline
|
||||
@ -174,6 +184,8 @@ void prvvMBTCPLogFrame( const CHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen
|
||||
void vMBPortSetMode( UCHAR ucMode );
|
||||
UCHAR ucMBPortGetMode( void );
|
||||
|
||||
BOOL xMBPortSerialWaitEvent(QueueHandle_t xMbUartQueue, uart_event_t* pxEvent, ULONG xTimeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
PR_END_EXTERN_C
|
||||
#endif /* __cplusplus */
|
@ -33,6 +33,8 @@
|
||||
*
|
||||
* File: $Id: portother.c,v 1.1 2010/06/06 13:07:20 wolti Exp $
|
||||
*/
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "port.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/queue.h" // for queue support
|
||||
@ -85,15 +87,14 @@ static USHORT usMBPortSerialRxPoll(size_t xEventSize)
|
||||
|
||||
if (bRxStateEnabled) {
|
||||
// Get received packet into Rx buffer
|
||||
while(xReadStatus && (usCnt++ <= MB_SERIAL_BUF_SIZE)) {
|
||||
while(xReadStatus && (usCnt++ <= xEventSize)) {
|
||||
// Call the Modbus stack callback function and let it fill the buffers.
|
||||
xReadStatus = pxMBFrameCBByteReceived(); // callback to execute receive FSM
|
||||
}
|
||||
uart_flush_input(ucUartNumber);
|
||||
// Send event EV_FRAME_RECEIVED to allow stack process packet
|
||||
#if !CONFIG_FMB_TIMER_PORT_ENABLED
|
||||
// Let the stack know that T3.5 time is expired and data is received
|
||||
(void)pxMBPortCBTimerExpired(); // calls callback xMBRTUTimerT35Expired();
|
||||
pxMBPortCBTimerExpired();
|
||||
#endif
|
||||
ESP_LOGD(TAG, "RX: %d bytes\n", usCnt);
|
||||
}
|
||||
@ -126,7 +127,7 @@ static void vUartTask(void *pvParameters)
|
||||
uart_event_t xEvent;
|
||||
USHORT usResult = 0;
|
||||
for(;;) {
|
||||
if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) {
|
||||
if (xMBPortSerialWaitEvent(xMbUartQueue, (void*)&xEvent, portMAX_DELAY)) {
|
||||
ESP_LOGD(TAG, "MB_uart[%d] event:", ucUartNumber);
|
||||
switch(xEvent.type) {
|
||||
//Event of UART receving data
|
||||
@ -135,6 +136,8 @@ static void vUartTask(void *pvParameters)
|
||||
// This flag set in the event means that no more
|
||||
// data received during configured timeout and UART TOUT feature is triggered
|
||||
if (xEvent.timeout_flag) {
|
||||
// Get buffered data length
|
||||
ESP_ERROR_CHECK(uart_get_buffered_data_len(ucUartNumber, &xEvent.size));
|
||||
// Read received data and send it to modbus stack
|
||||
usResult = usMBPortSerialRxPoll(xEvent.size);
|
||||
ESP_LOGD(TAG,"Timeout occured, processed: %d bytes", usResult);
|
||||
@ -219,7 +222,7 @@ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 2,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
.source_clk = UART_SCLK_APB
|
||||
};
|
||||
// Set UART config
|
||||
xErr = uart_param_config(ucUartNumber, &xUartConfig);
|
||||
@ -257,6 +260,17 @@ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL xMBSerialPortGetRequest( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength )
|
||||
{
|
||||
BOOL eStatus = TRUE;
|
||||
return eStatus;
|
||||
}
|
||||
|
||||
BOOL xMBSerialPortSendResponse( UCHAR *pucMBSerialFrame, USHORT usSerialLength )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void vMBPortSerialClose(void)
|
||||
{
|
||||
(void)vTaskSuspend(xMbTaskHandle);
|
@ -89,7 +89,7 @@ static USHORT usMBMasterPortSerialRxPoll(size_t xEventSize)
|
||||
USHORT usCnt = 0;
|
||||
|
||||
if (bRxStateEnabled) {
|
||||
while(xReadStatus && (usCnt++ <= MB_SERIAL_BUF_SIZE)) {
|
||||
while(xReadStatus && (usCnt++ <= xEventSize)) {
|
||||
// Call the Modbus stack callback function and let it fill the stack buffers.
|
||||
xReadStatus = pxMBMasterFrameCBByteReceived(); // callback to receive FSM
|
||||
}
|
||||
@ -134,7 +134,7 @@ static void vUartTask(void* pvParameters)
|
||||
uart_event_t xEvent;
|
||||
USHORT usResult = 0;
|
||||
for(;;) {
|
||||
if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) {
|
||||
if (xMBPortSerialWaitEvent(xMbUartQueue, (void*)&xEvent, portMAX_DELAY)) {
|
||||
ESP_LOGD(TAG, "MB_uart[%d] event:", ucUartNumber);
|
||||
switch(xEvent.type) {
|
||||
//Event of UART receiving data
|
||||
@ -143,6 +143,8 @@ static void vUartTask(void* pvParameters)
|
||||
// This flag set in the event means that no more
|
||||
// data received during configured timeout and UART TOUT feature is triggered
|
||||
if (xEvent.timeout_flag) {
|
||||
// Get buffered data length
|
||||
ESP_ERROR_CHECK(uart_get_buffered_data_len(ucUartNumber, &xEvent.size));
|
||||
// Read received data and send it to modbus stack
|
||||
usResult = usMBMasterPortSerialRxPoll(xEvent.size);
|
||||
ESP_LOGD(TAG,"Timeout occured, processed: %d bytes", usResult);
|
||||
@ -266,6 +268,24 @@ BOOL xMBMasterPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function is called from ASCII/RTU module to get processed data buffer. Sets the
|
||||
* received buffer and its length using parameters.
|
||||
*/
|
||||
BOOL xMBMasterSerialPortGetResponse( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function is called from ASCII/RTU module to set processed data buffer
|
||||
* to be sent in transmitter state machine.
|
||||
*/
|
||||
BOOL xMBMasterSerialPortSendRequest( UCHAR *pucMBSerialFrame, USHORT usSerialLength )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void vMBMasterPortSerialClose(void)
|
||||
{
|
||||
(void)vTaskDelete(xMbTaskHandle);
|
@ -74,7 +74,7 @@ BOOL xMBPortTimersInit(USHORT usTimeOut50us)
|
||||
esp_timer_create_args_t xTimerConf = {
|
||||
.callback = vTimerAlarmCBHandler,
|
||||
.arg = NULL,
|
||||
#if CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD
|
||||
#if (MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD && CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD)
|
||||
.dispatch_method = ESP_TIMER_ISR,
|
||||
#else
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
@ -71,7 +71,7 @@ BOOL xMBMasterPortTimersInit(USHORT usTimeOut50us)
|
||||
esp_timer_create_args_t xTimerConf = {
|
||||
.callback = vTimerAlarmCBHandler,
|
||||
.arg = NULL,
|
||||
#if CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD
|
||||
#if (MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD && CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD)
|
||||
.dispatch_method = ESP_TIMER_ISR,
|
||||
#else
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
@ -30,7 +30,7 @@ extern BOOL xMBMasterPortSerialTxPoll(void);
|
||||
#define MB_RESPONSE_TICS pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND + 10)
|
||||
|
||||
|
||||
static mb_master_interface_t* mbm_interface_ptr = NULL; //&default_interface_inst;
|
||||
static mb_master_interface_t* mbm_interface_ptr = NULL;
|
||||
static const char *TAG = "MB_CONTROLLER_MASTER";
|
||||
|
||||
// Modbus event processing task
|
||||
@ -101,7 +101,7 @@ static esp_err_t mbc_serial_master_start(void)
|
||||
"mb stack initialization failure, eMBInit() returns (0x%x).", status);
|
||||
status = eMBMasterEnable();
|
||||
MB_MASTER_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE,
|
||||
"mb stack set slave ID failure, eMBEnable() returned (0x%x).", (uint32_t)status);
|
||||
"mb stack set slave ID failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status);
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupSetBits(mbm_opts->mbm_event_group,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
@ -159,7 +159,7 @@ static esp_err_t mbc_tcp_master_start(void)
|
||||
|
||||
status = eMBMasterEnable();
|
||||
MB_MASTER_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE,
|
||||
"mb stack set slave ID failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status);
|
||||
"mb stack enable failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status);
|
||||
|
||||
// Add slave IP address for each slave to initialize connection
|
||||
mb_slave_addr_entry_t *p_slave_info;
|
||||
@ -172,13 +172,12 @@ static esp_err_t mbc_tcp_master_start(void)
|
||||
// Add end of list condition
|
||||
(void)xMBTCPPortMasterAddSlaveIp(0xFF, NULL, 0xFF);
|
||||
|
||||
|
||||
// Wait for connection done event
|
||||
bool start = (bool)xMBTCPPortMasterWaitEvent(mbm_opts->mbm_event_group,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED, MB_TCP_CONNECTION_TOUT);
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED, MB_TCP_CONNECTION_TOUT);
|
||||
MB_MASTER_CHECK((start), ESP_ERR_INVALID_STATE,
|
||||
"mb stack could not connect to slaves for %d seconds.",
|
||||
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC);
|
||||
"mb stack could not connect to slaves for %d seconds.",
|
||||
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
@ -560,6 +560,12 @@ static void vMBTCPPortServerTask(void *pvParameters)
|
||||
pxClientInfo->xSockId, pxClientInfo->pcIpAddr, xErr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (xShutdownSemaphore) {
|
||||
xSemaphoreGive(xShutdownSemaphore);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// Close client connection
|
||||
xMBTCPPortCloseConnection(pxClientInfo);
|
||||
|
10
idf_component.yml
Normal file
10
idf_component.yml
Normal file
@ -0,0 +1,10 @@
|
||||
version: "0.0.2"
|
||||
description: ESP-MODBUS is the official Modbus library for Espressif SoCs.
|
||||
dependencies:
|
||||
idf: ">=4.1"
|
||||
files:
|
||||
exclude:
|
||||
- "docs/_build/**/*"
|
||||
- "docs/_build"
|
||||
- "test/**/build/**/*"
|
||||
- "test/**/build"
|
9
test/README.md
Normal file
9
test/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Test implementation
|
||||
|
||||
This directory contains a set of ESP-IDF projects to be used as tests only, which aim to exercise various
|
||||
configuration of components to check completely arbitrary functionality should it be building only, executing under
|
||||
various conditions or combination with other components, including custom test frameworks.
|
||||
|
||||
The tests in this folder are not intended to demonstrate the ESP-IDF functionality in any way.
|
||||
|
||||
The examples can be found here: https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus
|
@ -2,5 +2,4 @@
|
||||
# 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"
|
||||
PRIV_REQUIRES freemodbus)
|
||||
INCLUDE_DIRS "include")
|
5
test/mb_example_common/component.mk
Normal file
5
test/mb_example_common/component.mk
Normal file
@ -0,0 +1,5 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := .
|
13
test/serial/mb_serial_master/CMakeLists.txt
Normal file
13
test/serial/mb_serial_master/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../../../")
|
||||
set(EXCLUDE_COMPONENTS examples test_app test freemodbus)
|
||||
|
||||
# Include parameters from common modbus folder
|
||||
set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_master)
|
12
test/serial/mb_serial_master/Makefile
Normal file
12
test/serial/mb_serial_master/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_master
|
||||
|
||||
EXTRA_COMPONENT_DIRS := ../../../
|
||||
EXTRA_COMPONENT_DIRS += ../../mb_example_common
|
||||
EXCLUDE_COMPONENTS := examples test freemodbus
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
4
test/serial/mb_serial_master/main/component.mk
Normal file
4
test/serial/mb_serial_master/main/component.mk
Normal file
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -3,10 +3,8 @@
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=y
|
||||
CONFIG_FMB_TIMER_GROUP=0
|
||||
CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=n
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
14
test/serial/mb_serial_slave/CMakeLists.txt
Normal file
14
test/serial/mb_serial_slave/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../../../")
|
||||
set(EXCLUDE_COMPONENTS examples test_app test freemodbus)
|
||||
|
||||
# Include parameters from common modbus folder
|
||||
set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(modbus_slave)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user