forked from espressif/esp-modbus
Merge branch 'release/v2.0' into 'main'
esp-modbus stack release v2.0.1 See merge request idf/esp-modbus!89
This commit is contained in:
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
.config
|
||||
*.o
|
||||
*.pyc
|
||||
|
||||
# gtags
|
||||
GTAGS
|
||||
GRTAGS
|
||||
GPATH
|
||||
|
||||
# emacs
|
||||
.dir-locals.el
|
||||
|
||||
# emacs temp file suffixes
|
||||
*~
|
||||
.#*
|
||||
\#*#
|
||||
|
||||
# eclipse setting
|
||||
.settings
|
||||
|
||||
# MacOS directory files
|
||||
.DS_Store
|
||||
|
||||
# Build related files
|
||||
build*
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
managed_components
|
||||
dependencies.lock
|
||||
.vscode
|
||||
doxygen_output/**
|
||||
dist
|
||||
|
||||
# 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/
|
||||
/.pytest_cache/
|
348
.gitlab-ci.yml
Normal file
348
.gitlab-ci.yml
Normal file
@@ -0,0 +1,348 @@
|
||||
stages:
|
||||
- build
|
||||
- target_test
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
# System environment
|
||||
ESP_DOCS_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env-v5.3:1-1"
|
||||
ESP_DOCS_PATH: "$CI_PROJECT_DIR"
|
||||
TEST_DIR: "$CI_PROJECT_DIR"
|
||||
|
||||
# GitLab-CI environment
|
||||
GET_SOURCES_ATTEMPTS: "10"
|
||||
ARTIFACT_DOWNLOAD_ATTEMPTS: "10"
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# Define a matrix for IDF versions and their corresponding targets
|
||||
.options_list:
|
||||
# versions:
|
||||
# IDF_VER: ["latest", "v5.3", "v5.2", "v5.0"]
|
||||
markers:
|
||||
TEST_MARKER:
|
||||
- "tcp"
|
||||
- "serial"
|
||||
- "generic"
|
||||
# - "tcp_p4"
|
||||
# - "serial_p4"
|
||||
|
||||
.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
|
||||
|
||||
.build_template:
|
||||
stage: build
|
||||
tags:
|
||||
- build
|
||||
variables:
|
||||
SIZE_INFO_LOCATION: "${TEST_DIR}/size_info.txt"
|
||||
IDF_CCACHE_ENABLE: "1"
|
||||
after_script:
|
||||
# Show ccache statistics if enabled globally
|
||||
- test "$CI_CCACHE_STATS" == 1 && test -n "$(which ccache)" && ccache --show-stats || true
|
||||
dependencies: []
|
||||
|
||||
.before_script_build_jobs:
|
||||
before_script:
|
||||
- pip install idf-component-manager --upgrade
|
||||
- pip install "idf_build_apps~=1.0.1"
|
||||
|
||||
.check_idf_ver: &check_idf_ver |
|
||||
export IDF_PATH=$(find /opt -type d -name "*idf*" \
|
||||
\( -exec test -f '{}/tools/idf.py' \; -and -exec test -f '{}/tools/idf_tools.py' \; \
|
||||
\) -print -quit)
|
||||
if [ -z "${IDF_PATH}" ];then
|
||||
echo "IDF version is not found."
|
||||
else
|
||||
cd ${IDF_PATH}
|
||||
export IDF_DESCRIBE=$(git describe)
|
||||
export IDF_VERSION=${IDF_DESCRIBE%-*}
|
||||
echo "$IDF_VERSION" >> $TEST_DIR/idf_version_info.txt
|
||||
echo "ESP-IDF: $IDF_VERSION"
|
||||
fi
|
||||
|
||||
# Note: this script builds the folder against all targets and then deletes
|
||||
# all other artifacts except the esp32 to decrease the size of artifacs (may cause failures)
|
||||
.build_cur_folder: &build_cur_folder |
|
||||
echo "Build job ${CI_JOB_NAME}, folder: ${PWD##*/}, targets: ${TEST_TARGETS}"
|
||||
python -m idf_build_apps build -v -p . \
|
||||
--recursive \
|
||||
--target all \
|
||||
--default-build-targets ${TEST_TARGETS} \
|
||||
--config "sdkconfig.ci.*=" --build-dir "build_@t_@w" \
|
||||
--check-warnings \
|
||||
--ignore-warning-file ../tools/ignore_build_warnings.txt \
|
||||
--collect-size-info $SIZE_INFO_LOCATION \
|
||||
--manifest-rootpath . \
|
||||
--manifest-file .build-test-rules.yml \
|
||||
--parallel-count ${CI_NODE_TOTAL:-1} \
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
echo "delete build folders:" $(find . -type d -regex '^\./.*build_esp32[a-z]+[0-9]+[_a-z]*' -print -exec rm -rf {} +)
|
||||
ls -lh > test_dir_${PWD##*/}.txt
|
||||
|
||||
# This template gets expanded multiple times, once for every IDF version.
|
||||
# IDF version is specified by setting the espressif/idf image tag.
|
||||
#
|
||||
# TEST_TARGETS sets the list of IDF_TARGET values to build the test for.
|
||||
# It should contain only the targets with optimized assembly implementations.
|
||||
#
|
||||
.build_pytest_template:
|
||||
stage: build
|
||||
extends:
|
||||
- .build_template
|
||||
- .before_script_build_jobs
|
||||
script:
|
||||
# The script below will build all test applications defined in environment variable $TEST_TARGETS
|
||||
- *check_idf_ver
|
||||
# This is workaround to build library under esp-idf v4.4
|
||||
- pip install idf-component-manager --upgrade
|
||||
- cd ${TEST_DIR}/test_apps
|
||||
- *build_cur_folder
|
||||
- cd ${TEST_DIR}/examples
|
||||
- export TEST_TARGETS="esp32" # override to build only on esp32 target to decrease build time
|
||||
- *build_cur_folder
|
||||
variables:
|
||||
TEST_TARGETS: "esp32"
|
||||
artifacts:
|
||||
name: artifacts_${CI_JOB_NAME}
|
||||
paths:
|
||||
- "**/build*/size.json"
|
||||
- "**/build*/build.log"
|
||||
- "**/build*/build_log.txt"
|
||||
- "**/build*/*.bin"
|
||||
- "**/build*/*.elf"
|
||||
- "**/build*/*.map"
|
||||
- "**/build*/flasher_args.json"
|
||||
- "**/build*/flash_project_args"
|
||||
- "**/build*/config/sdkconfig.json"
|
||||
- "**/build*/bootloader/*.bin"
|
||||
- "**/build*/partition_table/*.bin"
|
||||
- "**/idf_version_info.txt"
|
||||
- "**/test_dir*.txt"
|
||||
- $SIZE_INFO_LOCATION
|
||||
when: always
|
||||
expire_in: 3 weeks
|
||||
|
||||
build_idf_latest:
|
||||
extends: .build_pytest_template
|
||||
image: espressif/idf:latest
|
||||
variables:
|
||||
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c5 esp32c6 esp32h2"
|
||||
|
||||
build_idf_v5.3:
|
||||
extends: .build_pytest_template
|
||||
image: espressif/idf:release-v5.3
|
||||
variables:
|
||||
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2"
|
||||
|
||||
build_idf_v5.2:
|
||||
extends: .build_pytest_template
|
||||
image: espressif/idf:release-v5.2
|
||||
variables:
|
||||
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3"
|
||||
|
||||
build_idf_v5.0:
|
||||
extends: .build_pytest_template
|
||||
image: espressif/idf:release-v5.0
|
||||
variables:
|
||||
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3"
|
||||
|
||||
.target_test_template:
|
||||
stage: target_test
|
||||
timeout: 1 hour
|
||||
variables:
|
||||
GIT_DEPTH: 1
|
||||
SUBMODULES_TO_FETCH: "none"
|
||||
cache:
|
||||
# Usually do not need submodule-cache in target_test
|
||||
- key: pip-cache
|
||||
paths:
|
||||
- .cache/pip
|
||||
policy: pull
|
||||
|
||||
.before_script_pytest_jobs:
|
||||
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)
|
||||
echo "Start test job: ${CI_JOB_NAME}, version: ${IDF_VER%-*}, folder: ${PWD##*/}"
|
||||
python -m pytest --junit-xml=${TEST_DIR}/${PWD##*/}/results_${IDF_VER%-*}_${PWD##*/}.xml --target=${IDF_TARGET} -m multi_dut_modbus_${TEST_MARKER}
|
||||
ls -lh > test_dir_${PWD##*/}.txt
|
||||
|
||||
.test_template:
|
||||
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.3:1"
|
||||
stage: target_test
|
||||
extends:
|
||||
- .before_script_pytest_jobs
|
||||
tags:
|
||||
- multi_dut_modbus_${TEST_MARKER}
|
||||
variables:
|
||||
IDF_TARGET: "esp32" # the only esp32 runners are available for now
|
||||
script:
|
||||
- cd ${TEST_DIR}/test_apps/
|
||||
- *test_cur_folder
|
||||
- cd ${TEST_DIR}/examples/
|
||||
- *test_cur_folder
|
||||
artifacts:
|
||||
name: artifacts_${CI_JOB_NAME}
|
||||
paths:
|
||||
- "${TEST_DIR}/**/*.log"
|
||||
- "${TEST_DIR}/**/*.xml"
|
||||
- "${TEST_DIR}/**/results_*.xml"
|
||||
- "${TEST_DIR}/**/pytest_embedded_log/"
|
||||
- "${TEST_DIR}/**/test_dir*.txt"
|
||||
- "${TEST_DIR}/**/idf_version_info.txt"
|
||||
- "${TEST_DIR}/**/log.html"
|
||||
- "${TEST_DIR}/**/report.html"
|
||||
- "${TEST_DIR}/**/*.pcap"
|
||||
reports:
|
||||
junit: ${TEST_DIR}/results_${IDF_VER%-*}.xml
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
|
||||
target_test_latest:
|
||||
stage: target_test
|
||||
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.4:1"
|
||||
extends: .test_template
|
||||
parallel:
|
||||
matrix:
|
||||
- !reference [.options_list, markers]
|
||||
needs:
|
||||
job: build_idf_latest
|
||||
artifacts: true
|
||||
after_script: []
|
||||
|
||||
target_test_v5.3:
|
||||
stage: target_test
|
||||
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.3:1"
|
||||
extends: .test_template
|
||||
parallel:
|
||||
matrix:
|
||||
- !reference [.options_list, markers]
|
||||
needs:
|
||||
job: build_idf_v5.3
|
||||
artifacts: true
|
||||
after_script: []
|
||||
|
||||
target_test_v5.2:
|
||||
stage: target_test
|
||||
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.2:2"
|
||||
extends: .test_template
|
||||
parallel:
|
||||
matrix:
|
||||
- !reference [.options_list, markers]
|
||||
needs:
|
||||
job: build_idf_v5.2
|
||||
artifacts: true
|
||||
after_script: []
|
||||
|
||||
target_test_v5.0:
|
||||
stage: target_test
|
||||
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.0:3"
|
||||
extends: .test_template
|
||||
parallel:
|
||||
matrix:
|
||||
- !reference [.options_list, markers]
|
||||
needs:
|
||||
job: build_idf_v5.0
|
||||
artifacts: true
|
||||
after_script: []
|
||||
|
||||
build_docs:
|
||||
stage: build
|
||||
image: $ESP_DOCS_ENV_IMAGE
|
||||
tags:
|
||||
- build_docs
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- docs/_build/*/*/*.txt
|
||||
- docs/_build/*/*/html/*
|
||||
expire_in: 4 days
|
||||
# No cleaning when the artifacts
|
||||
after_script: []
|
||||
script:
|
||||
- cd docs
|
||||
- pip install -r requirements.txt
|
||||
- ./generate_docs
|
||||
|
||||
.deploy_docs_template:
|
||||
stage: deploy
|
||||
image: $ESP_DOCS_ENV_IMAGE
|
||||
tags:
|
||||
- deploy_docs
|
||||
needs:
|
||||
- build_docs
|
||||
only:
|
||||
changes:
|
||||
- "docs/**/*"
|
||||
script:
|
||||
- source ${CI_PROJECT_DIR}/docs/utils.sh
|
||||
- add_doc_server_ssh_keys $DOCS_DEPLOY_PRIVATEKEY $DOCS_DEPLOY_SERVER $DOCS_DEPLOY_SERVER_USER
|
||||
- export GIT_VER=$(git describe --always)
|
||||
- export GIT_TAGS=$(git fetch --tags; git tag -l;)
|
||||
- echo "Deploy ${PWD##*/}, ${DOCS_BUILD_DIR}, Ref name; ${CI_COMMIT_REF_NAME}"
|
||||
- echo "Version; ${GIT_VER}, tags; ${GIT_TAGS}"
|
||||
- pip install -r ${CI_PROJECT_DIR}/docs/requirements.txt
|
||||
- deploy-docs
|
||||
|
||||
deploy_docs_preview:
|
||||
extends:
|
||||
- .deploy_docs_template
|
||||
except:
|
||||
refs:
|
||||
- main
|
||||
variables:
|
||||
TYPE: "preview"
|
||||
DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/"
|
||||
DOCS_DEPLOY_PRIVATEKEY: "$DOCS_PREVIEW_PRIVATEKEY"
|
||||
DOCS_DEPLOY_SERVER: "$DOCS_PREVIEW_SERVER"
|
||||
DOCS_DEPLOY_SERVER_USER: "$DOCS_PREVIEW_SERVER_USER"
|
||||
DOCS_DEPLOY_PATH: "$DOCS_PREVIEW_PATH"
|
||||
DOCS_DEPLOY_URL_BASE: "$DOCS_PREVIEW_URL_BASE"
|
||||
|
||||
deploy_docs_production:
|
||||
extends:
|
||||
- .deploy_docs_template
|
||||
only:
|
||||
refs:
|
||||
- main
|
||||
variables:
|
||||
TYPE: "production"
|
||||
DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/"
|
||||
DOCS_DEPLOY_PRIVATEKEY: "$DOCS_PROD_DEPLOY_KEY"
|
||||
DOCS_DEPLOY_SERVER: "$DOCS_PROD_SERVER"
|
||||
DOCS_DEPLOY_SERVER_USER: "$DOCS_PROD_SERVER_USER"
|
||||
DOCS_DEPLOY_PATH: "$DOCS_PROD_PATH"
|
||||
DOCS_DEPLOY_URL_BASE: "https://docs.espressif.com/projects/esp-modbus"
|
||||
|
||||
upload_to_component_manager:
|
||||
stage: deploy
|
||||
image: python:3.10-alpine
|
||||
tags:
|
||||
- deploy
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"' # the main branch is a component release branch for v2
|
||||
- if: '$FORCE_PUSH_COMPONENT == "1"'
|
||||
when: manual
|
||||
script:
|
||||
- pip install idf-component-manager
|
||||
- export IDF_COMPONENT_API_TOKEN=${ESP_MODBUS_API_KEY}
|
||||
- export COMP_VERSION=$(grep 'version:' idf_component.yml | head -n 1 | awk '{print $2}' | tr -d '"')
|
||||
- compote component upload --namespace=espressif --name=esp-modbus --allow-existing --version=${COMP_VERSION}
|
19
.pre-commit-config.yaml
Normal file
19
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
repos:
|
||||
- repo: https://github.com/igrr/astyle_py.git
|
||||
rev: master
|
||||
hooks:
|
||||
- id: astyle_py
|
||||
args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper']
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
types_or: [c, c++]
|
||||
- id: end-of-file-fixer
|
||||
types_or: [c, c++]
|
||||
- id: check-merge-conflict
|
||||
- id: mixed-line-ending
|
||||
types_or: [c, c++]
|
||||
args: ['--fix=lf']
|
||||
description: Forces to replace line ending by the UNIX 'lf' character
|
92
CMakeLists.txt
Normal file
92
CMakeLists.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
# 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
|
||||
"mb_controller/common/esp_modbus_master.c"
|
||||
"mb_controller/common/esp_modbus_slave.c"
|
||||
"mb_controller/common/esp_modbus_master_serial.c"
|
||||
"mb_controller/common/esp_modbus_slave_serial.c"
|
||||
"mb_controller/common/esp_modbus_master_tcp.c"
|
||||
"mb_controller/common/esp_modbus_slave_tcp.c"
|
||||
"mb_controller/serial/mbc_serial_master.c"
|
||||
"mb_controller/serial/mbc_serial_slave.c"
|
||||
"mb_controller/tcp/mbc_tcp_master.c"
|
||||
"mb_controller/tcp/mbc_tcp_slave.c"
|
||||
"mb_objects/mb_master.c"
|
||||
"mb_objects/mb_slave.c"
|
||||
"mb_objects/functions/mbfunccoils_master.c"
|
||||
"mb_objects/functions/mbfunccoils.c"
|
||||
"mb_objects/functions/mbfuncdiag.c"
|
||||
"mb_objects/functions/mbfuncdisc_master.c"
|
||||
"mb_objects/functions/mbfuncdisc.c"
|
||||
"mb_objects/functions/mbfuncholding_master.c"
|
||||
"mb_objects/functions/mbfuncholding.c"
|
||||
"mb_objects/functions/mbfuncinput_master.c"
|
||||
"mb_objects/functions/mbfuncinput.c"
|
||||
"mb_objects/functions/mbfuncother.c"
|
||||
"mb_objects/functions/mbutils.c"
|
||||
"mb_ports/common/port_event.c"
|
||||
"mb_ports/common/port_other.c"
|
||||
"mb_ports/common/port_timer.c"
|
||||
"mb_ports/common/mb_transaction.c"
|
||||
"mb_ports/serial/port_serial.c"
|
||||
"mb_ports/tcp/port_tcp_master.c"
|
||||
"mb_ports/tcp/port_tcp_slave.c"
|
||||
"mb_ports/tcp/port_tcp_driver.c"
|
||||
"mb_ports/tcp/port_tcp_utils.c"
|
||||
"mb_transports/rtu/rtu_master.c"
|
||||
"mb_transports/rtu/rtu_slave.c"
|
||||
"mb_transports/rtu/mbcrc.c"
|
||||
"mb_transports/ascii/ascii_master.c"
|
||||
"mb_transports/ascii/ascii_slave.c"
|
||||
"mb_transports/ascii/ascii_lrc.c"
|
||||
"mb_transports/tcp/tcp_master.c"
|
||||
"mb_transports/tcp/tcp_slave.c"
|
||||
)
|
||||
|
||||
set(include_dirs mb_transports mb_controller/common/include mb_objects/include mb_ports/common mb_ports/serial mb_ports/tcp)
|
||||
|
||||
set(priv_include_dirs mb_controller/serial mb_controller/tcp mb_controller/common mb_transports/rtu mb_transports/ascii mb_transports/tcp)
|
||||
|
||||
if(CONFIG_FMB_EXT_TYPE_SUPPORT)
|
||||
list(APPEND srcs "mb_controller/common/mb_endianness_utils.c")
|
||||
endif()
|
||||
|
||||
add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${srcs})
|
||||
add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${include_dirs})
|
||||
add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${priv_include_dirs})
|
||||
|
||||
message(STATUS "DEBUG: Use esp-modbus component folder: ${CMAKE_CURRENT_LIST_DIR}.")
|
||||
|
||||
set(requires driver)
|
||||
set(priv_requires esp_netif esp_event vfs)
|
||||
|
||||
# esp_timer component was introduced in v4.2
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.1")
|
||||
list(APPEND requires esp_timer)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "${include_dirs}"
|
||||
PRIV_INCLUDE_DIRS "${priv_include_dirs}"
|
||||
REQUIRES ${requires}
|
||||
PRIV_REQUIRES ${priv_requires}
|
||||
LDFRAGMENTS linker.lf)
|
||||
|
||||
# This is an alternative of macro `idf_component_optional_requires(PUBLIC mdns)` to support all versions of esp-idf
|
||||
set(optional_reqs mdns espressif__mdns)
|
||||
idf_build_get_property(build_components BUILD_COMPONENTS)
|
||||
message(STATUS "build_components = ${build_components}")
|
||||
foreach(req ${optional_reqs} ${exclude_comps})
|
||||
if(req IN_LIST build_components)
|
||||
idf_component_get_property(req_lib ${req} COMPONENT_LIB)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE ${req_lib})
|
||||
message(STATUS "Req ${req} is found and added into ${COMPONENT_NAME} dependencies.")
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DMB_MDNS_IS_INCLUDED)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# target_link_options(${COMPONENT_LIB} INTERFACE -fsanitize=undefined -fsanitize=alignment) #-fsanitize=address -fsanitize=undefined
|
||||
# target_link_options(${COMPONENT_LIB} INTERFACE -fsanitize=address)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-strict-aliasing -Wno-write-strings -Werror)
|
||||
|
||||
message(STATUS "The mdns included is: ${MB_MDNS_IS_INCLUDED}")
|
218
Kconfig
Normal file
218
Kconfig
Normal file
@@ -0,0 +1,218 @@
|
||||
menu "Modbus configuration"
|
||||
|
||||
config FMB_COMM_MODE_TCP_EN
|
||||
bool "Enable Modbus stack support for TCP communication mode"
|
||||
default y
|
||||
help
|
||||
Enable Modbus TCP option for stack.
|
||||
|
||||
config FMB_TCP_PORT_DEFAULT
|
||||
int "Modbus TCP port number"
|
||||
range 0 65535
|
||||
default 502
|
||||
depends on FMB_COMM_MODE_TCP_EN
|
||||
help
|
||||
Modbus default port number used by Modbus TCP stack
|
||||
|
||||
config FMB_TCP_PORT_MAX_CONN
|
||||
int "Maximum allowed connections for TCP stack"
|
||||
range 1 8
|
||||
default 5
|
||||
depends on FMB_COMM_MODE_TCP_EN
|
||||
help
|
||||
Maximum allowed connections number for Modbus TCP stack.
|
||||
This is used by Modbus master and slave port layer to establish connections.
|
||||
This parameter may decrease performance of Modbus stack and can cause
|
||||
increasing of processing time (increase only if absolutely necessary).
|
||||
|
||||
config FMB_TCP_CONNECTION_TOUT_SEC
|
||||
int "Modbus TCP connection timeout"
|
||||
range 1 7200
|
||||
default 20
|
||||
depends on FMB_COMM_MODE_TCP_EN
|
||||
help
|
||||
Modbus TCP connection timeout in seconds.
|
||||
Once expired the current connection with the client will be closed
|
||||
and Modbus slave will be waiting for new connection to accept.
|
||||
|
||||
config FMB_TCP_UID_ENABLED
|
||||
bool "Modbus TCP enable UID (Unit Identifier) support"
|
||||
default n
|
||||
depends on FMB_COMM_MODE_TCP_EN
|
||||
help
|
||||
If this option is set the Modbus stack uses UID (Unit Identifier) field in MBAP frame.
|
||||
Else the UID is ignored by master and slave.
|
||||
|
||||
config FMB_COMM_MODE_RTU_EN
|
||||
bool "Enable Modbus stack support for RTU mode"
|
||||
default y
|
||||
help
|
||||
Enable RTU Modbus communication mode option for Modbus serial stack.
|
||||
|
||||
config FMB_COMM_MODE_ASCII_EN
|
||||
bool "Enable Modbus stack support for ASCII mode"
|
||||
default y
|
||||
help
|
||||
Enable ASCII Modbus communication mode option for Modbus serial stack.
|
||||
|
||||
config FMB_MASTER_TIMEOUT_MS_RESPOND
|
||||
int "Slave respond timeout (Milliseconds)"
|
||||
default 10000
|
||||
range 150 30000
|
||||
help
|
||||
If master sends a frame which is not broadcast, it has to wait some time for slave response.
|
||||
if slave is not respond in this time, the master will process timeout error.
|
||||
|
||||
config FMB_MASTER_DELAY_MS_CONVERT
|
||||
int "Slave conversion delay (Milliseconds)"
|
||||
default 200
|
||||
range 150 2000
|
||||
help
|
||||
If master sends a broadcast frame, it has to wait conversion time to delay,
|
||||
then master can send next frame.
|
||||
|
||||
config FMB_QUEUE_LENGTH
|
||||
int "Modbus serial task queue length"
|
||||
range 0 200
|
||||
default 20
|
||||
help
|
||||
Modbus serial driver queue length. It is used by event queue task.
|
||||
See the serial driver API for more information.
|
||||
|
||||
config FMB_PORT_TASK_STACK_SIZE
|
||||
int "Modbus port task stack size"
|
||||
range 2048 16384
|
||||
default 4096
|
||||
help
|
||||
Modbus port task stack size for rx/tx event processing.
|
||||
It may be adjusted when debugging is enabled (for example).
|
||||
|
||||
config FMB_BUFFER_SIZE
|
||||
int "Modbus RX/TX buffer size"
|
||||
range 256 2048
|
||||
default 260 if FMB_COMM_MODE_TCP_EN
|
||||
default 256 if !FMB_COMM_MODE_TCP_EN
|
||||
help
|
||||
Modbus RX/TX buffer size for UART driver initialization.
|
||||
This buffer is used for modbus frame transfer. The Modbus protocol maximum
|
||||
frame size is 260 bytes (TCP). Bigger size can be used for non standard implementations.
|
||||
|
||||
config FMB_SERIAL_ASCII_BITS_PER_SYMB
|
||||
int "Number of data bits per ASCII character"
|
||||
default 8
|
||||
range 7 8
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
help
|
||||
This option defines the number of data bits per ASCII character.
|
||||
|
||||
config FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS
|
||||
int "Response timeout for ASCII communication mode (ms)"
|
||||
default 1000
|
||||
range 200 5000
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
help
|
||||
This option defines response timeout of slave in milliseconds for ASCII communication mode.
|
||||
Thus the timeout will expire and allow the master program to handle the error.
|
||||
|
||||
config FMB_PORT_TASK_PRIO
|
||||
int "Modbus port task priority"
|
||||
range 3 23
|
||||
default 10
|
||||
help
|
||||
Modbus port data processing task priority.
|
||||
The priority of Modbus controller task is equal to (CONFIG_FMB_PORT_TASK_PRIO - 1).
|
||||
|
||||
choice FMB_PORT_TASK_AFFINITY
|
||||
prompt "Modbus task affinity"
|
||||
default FMB_PORT_TASK_AFFINITY_CPU0
|
||||
depends on !FREERTOS_UNICORE
|
||||
help
|
||||
Allows setting the core affinity of the Modbus controller task, i.e. whether the task is pinned to
|
||||
particular CPU, or allowed to run on any CPU.
|
||||
|
||||
config FMB_PORT_TASK_AFFINITY_NO_AFFINITY
|
||||
bool "No affinity"
|
||||
config FMB_PORT_TASK_AFFINITY_CPU0
|
||||
bool "CPU0"
|
||||
config FMB_PORT_TASK_AFFINITY_CPU1
|
||||
bool "CPU1"
|
||||
|
||||
endchoice
|
||||
|
||||
config FMB_PORT_TASK_AFFINITY
|
||||
hex
|
||||
default FREERTOS_NO_AFFINITY if FMB_PORT_TASK_AFFINITY_NO_AFFINITY || FREERTOS_UNICORE
|
||||
default 0x0 if FMB_PORT_TASK_AFFINITY_CPU0
|
||||
default 0x1 if FMB_PORT_TASK_AFFINITY_CPU1
|
||||
|
||||
config FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
bool "Modbus controller slave ID support"
|
||||
default y
|
||||
help
|
||||
Modbus slave ID support enable.
|
||||
When enabled the Modbus <Report Slave ID> command is supported by stack.
|
||||
|
||||
config FMB_CONTROLLER_SLAVE_ID
|
||||
hex "Modbus controller slave ID"
|
||||
range 0 4294967295
|
||||
default 0x00112233
|
||||
depends on FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
help
|
||||
Modbus slave ID value to identify modbus device
|
||||
in the network using <Report Slave ID> command.
|
||||
Most significant byte of ID is used as short device ID and
|
||||
other three bytes used as long ID.
|
||||
|
||||
config FMB_CONTROLLER_NOTIFY_TIMEOUT
|
||||
int "Modbus controller notification timeout (ms)"
|
||||
range 0 200
|
||||
default 20
|
||||
help
|
||||
Modbus controller notification timeout in milliseconds.
|
||||
This timeout is used to send notification about accessed parameters.
|
||||
|
||||
config FMB_CONTROLLER_NOTIFY_QUEUE_SIZE
|
||||
int "Modbus controller notification queue size"
|
||||
range 0 200
|
||||
default 20
|
||||
help
|
||||
Modbus controller notification queue size.
|
||||
The notification queue is used to get information about accessed parameters.
|
||||
|
||||
config FMB_CONTROLLER_STACK_SIZE
|
||||
int "Modbus controller stack size"
|
||||
range 2048 32768
|
||||
default 4096
|
||||
help
|
||||
Modbus controller task stack size. The Stack size may be adjusted when
|
||||
debug mode is used which requires more stack size (for example).
|
||||
|
||||
config FMB_EVENT_QUEUE_TIMEOUT
|
||||
int "Modbus stack event queue timeout (ms)"
|
||||
range 10 500
|
||||
default 20
|
||||
help
|
||||
Modbus stack event queue timeout in milliseconds. This may help to optimize
|
||||
Modbus stack event processing time.
|
||||
|
||||
config FMB_TIMER_USE_ISR_DISPATCH_METHOD
|
||||
bool "Modbus timer uses ISR dispatch method"
|
||||
default n
|
||||
select ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
|
||||
select UART_ISR_IN_IRAM
|
||||
help
|
||||
If this option is set the Modbus stack uses ISR dispatch method
|
||||
to send timeout events from the callback function called from ISR.
|
||||
This option has dependency with the UART_ISR_IN_IRAM option which places UART interrupt
|
||||
handler into IRAM to prevent delays related to processing of UART events.
|
||||
|
||||
config FMB_EXT_TYPE_SUPPORT
|
||||
bool "Modbus uses extended types to support third party devices"
|
||||
default n
|
||||
help
|
||||
If this option is set the Modbus stack supports extended list of types
|
||||
in data dictionary and conversion API to work with the extended types
|
||||
otherwise the only legacy types are supported. The extended types include
|
||||
integer, float, double types with different endianness and size.
|
||||
|
||||
endmenu
|
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# ESP-Modbus Library
|
||||
|
||||
## Overview
|
||||
|
||||
An Espressif ESP-Modbus Library (esp-modbus) is a library to support Modbus communication in the networks based on RS485, WiFi, Ethernet interfaces. The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs).
|
||||
|
||||
* [ESP-Modbus component on GitHub](https://github.com/espressif/esp-modbus/tree/main)
|
||||
|
||||
This library is to be used with Espressif’s IoT Development Framework, [ESP_IDF](https://github.com/espressif/esp-idf). The packages from this repository are uploaded to Espressif’s component repository.
|
||||
|
||||
* [esp-modbus component in component repository](https://components.espressif.com/component/espressif/esp-modbus)
|
||||
|
||||
You can add the component to your project via `idf.py add-dependency`. More information about idf-component-manager can be found in [Espressif API guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html) or [PyPi registry](https://pypi.org/project/idf-component-manager).
|
||||
|
||||
The ESP-Modbus library can be used with ESP-IDF v5.0 and later. Some ESP-IDF releases include an earlier version of ESP-Modbus library inside freemodbus component. To use ESP-Modbus with these releases, users need to exclude the built-in freemodbus component from the build process, and update application components to depend on esp-modbus component instead. To exclude freemodbus component from compilation, add the following line to the project CMakeLists.txt file:
|
||||
|
||||
```
|
||||
set(EXCLUDE_COMPONENTS freemodbus)
|
||||
```
|
||||
|
||||
ESP-IDF v5.x and later releases do not include freemodbus component, so no extra steps are necessary when adding esp-modbus component.
|
||||
|
||||
## Peculiarities Of Current Release
|
||||
|
||||
The current release esp-modbus corresponds to the version `v2.x.x` (refer to idf_component.yml file) and supports creation of several instances of modbus master and slave objects. The public API interface is changed to allow creation of multiple communication objects with its own communication parameters, and the constructor API returns the handle to the interface structure that must be used as a first parameter for each API call for this particular object. For more information about the interface API and related changes see the official documentation for this release, described below. The goal of this beta release is to introduce new features and changes to the end users and get their feedback. The appropriate information or feature requests can be shared over on discussion page of the project.
|
||||
|
||||
* [Discussions](https://github.com/espressif/esp-modbus/discussions/categories/general)
|
||||
* [Issues](https://github.com/espressif/esp-modbus/issues)
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation can be found on the link below:
|
||||
|
||||
* [ESP-Modbus documentation (English)](https://docs.espressif.com/projects/esp-modbus/en/stable/esp32/index.html)
|
||||
|
||||
## Application Examples
|
||||
|
||||
The examples below demonstrate the ESP-Modbus library of serial, TCP ports for slave and master implementations accordingly.
|
||||
|
||||
- [Modbus Serial slave example](https://github.com/espressif/esp-modbus/tree/main/examples/serial/mb_serial_slave)
|
||||
|
||||
- [Modbus Serial master example](https://github.com/espressif/esp-modbus/tree/main/examples/serial/mb_serial_master)
|
||||
|
||||
- [Modbus TCP slave example](https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_slave)
|
||||
|
||||
- [Modbus TCP master example](https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_master)
|
||||
|
||||
Please refer to the specific example README.md for details.
|
||||
|
||||
## Protocol References
|
||||
|
||||
- [Modbus Organization with protocol specifications](https://modbus.org/specs.php)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions to this project in the form of bug reports, feature requests and pull requests.
|
||||
|
||||
Issue reports and feature requests can be submitted using Github Issues: https://github.com/espressif/esp-modbus/issues. Please check if the issue has already been reported before opening a new one.
|
||||
|
||||
Contributions in the form of pull requests should follow ESP-IDF project's [contribution guidelines](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/index.html). We kindly ask developers to start a discussion on an issue before proposing large changes to the project.
|
||||
|
||||
## Licence
|
||||
|
||||
The initial ESP-Modbus project was based on [FreeMODBUS library](https://github.com/cwalter-at/freemodbus), Copyright (c) 2006 Christian Walter and licensed under the BSD 3-clause license.
|
||||
|
||||
Modbus Master related code is Copyright (c) 2013 Armink and licensed under BSD 3-clause license.
|
||||
|
||||
All original code in this repository is Copyright (c) 2016-2022 Espressif Systems (Shanghai) Co. Ltd.
|
||||
|
||||
The project is distributed under Apache 2.0 license. See the accompanying [LICENSE file](https://github.com/espressif/esp-modbus/blob/master/LICENSE) for a copy.
|
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) \
|
||||
)
|
||||
|
||||
|
||||
|
58
docs/Doxyfile
Normal file
58
docs/Doxyfile
Normal file
@@ -0,0 +1,58 @@
|
||||
# This is Doxygen configuration file
|
||||
#
|
||||
# Doxygen provides over 260 configuration statements
|
||||
# To make this file easier to follow,
|
||||
# it contains only statements that are non-default
|
||||
#
|
||||
# NOTE:
|
||||
# It is recommended not to change defaults unless specifically required
|
||||
# Test any changes how they affect generated documentation
|
||||
# Make sure that correct warnings are generated to flag issues with documented code
|
||||
#
|
||||
# For the complete list of configuration statements see:
|
||||
# http://doxygen.nl/manual/config.html
|
||||
|
||||
|
||||
PROJECT_NAME = "IDF Programming Guide"
|
||||
|
||||
## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
|
||||
## to automatically generate API reference list files heder_file.inc
|
||||
## These files are placed in '_inc' directory
|
||||
## and used to include in API reference documentation
|
||||
|
||||
INPUT = \
|
||||
$(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_common.h \
|
||||
$(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_slave.h \
|
||||
$(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_master.h \
|
||||
$(PROJECT_PATH)/modbus/mb_controller/common/include/mb_endianness_utils.h
|
||||
|
||||
## Get warnings for functions that have no documentation for their parameters or return value
|
||||
##
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files
|
||||
##
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = YES
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
PREDEFINED = \
|
||||
$(ENV_DOXYGEN_DEFINES) \
|
||||
|
||||
## Do not complain about not having dot
|
||||
##
|
||||
HAVE_DOT = NO
|
||||
|
||||
## Generate XML that is required for Breathe
|
||||
##
|
||||
GENERATE_XML = YES
|
||||
XML_OUTPUT = xml
|
||||
|
||||
GENERATE_HTML = NO
|
||||
HAVE_DOT = NO
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = YES
|
||||
GENERATE_RTF = NO
|
||||
|
||||
## Skip distracting progress messages
|
||||
##
|
||||
QUIET = YES
|
11
docs/README.md
Normal file
11
docs/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# ESP-Modbus Library
|
||||
|
||||
This folder represents the official documentation for the ESP-Modbus library (**esp-modbus component documentation**). The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). The Modbus has become a de facto standard communication protocol and is now a commonly available means of connecting industrial electronic devices. This library supports Modbus communication in the networks that are based on RS485 or Ethernet interfaces.
|
||||
|
||||
# Hosted Documentation
|
||||
|
||||
* English: https://docs.espressif.com/projects/esp-modbus/
|
||||
|
||||
# Building Documentation
|
||||
|
||||
The documentation is built using the python package `esp-docs`, which can be installed by running `pip install esp-docs`. Running `build-docs --help` will give a summary of available options. For more information see the `esp-docs` documentation at https://github.com/espressif/esp-docs/blob/master/README.md
|
260
docs/_static/404-page__en.svg
vendored
Normal file
260
docs/_static/404-page__en.svg
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="图层_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 580"
|
||||
style="enable-background:new 0 0 1000 580;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:url(#polygon12_1_);}
|
||||
.st2{fill:url(#polygon21_1_);}
|
||||
.st3{opacity:0.27;fill:url(#circle42_1_);enable-background:new ;}
|
||||
.st4{fill:url(#polygon58_1_);}
|
||||
.st5{fill:#444444;stroke:#FFFFFF;stroke-width:0.834;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#FFFFFF;stroke-width:1.1033;stroke-miterlimit:10;}
|
||||
.st7{fill:none;stroke:#353535;stroke-width:1.1033;stroke-miterlimit:10;}
|
||||
.st8{fill:#FFFFFF;stroke:#444444;stroke-width:0.834;stroke-miterlimit:10;}
|
||||
.st9{fill:#444444;stroke:#FFFFFF;stroke-width:0.8485;stroke-miterlimit:10;}
|
||||
.st10{fill:none;stroke:#FFFFFF;stroke-width:1.1226;stroke-miterlimit:10;}
|
||||
.st11{fill:none;stroke:#353535;stroke-width:1.1226;stroke-miterlimit:10;}
|
||||
.st12{fill:#FFFFFF;stroke:#444444;stroke-width:0.8485;stroke-miterlimit:10;}
|
||||
.st13{fill:#353535;}
|
||||
.st14{fill:#444444;stroke:#FFFFFF;stroke-width:0.9321;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:1.046;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#353535;stroke-width:1.046;stroke-miterlimit:10;}
|
||||
.st17{fill:#FFFFFF;stroke:#444444;stroke-width:0.7906;stroke-miterlimit:10;}
|
||||
.st18{opacity:0.59;fill:#E0E0E0;enable-background:new ;}
|
||||
.st19{fill:#FFFFFF;stroke:#444444;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st20{fill:none;stroke:#444444;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#444444;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st22{enable-background:new ;}
|
||||
.st23{fill:#4D4D4D;}
|
||||
</style>
|
||||
<rect id="BG_2_" x="-1" y="-9.5" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st0" width="1012.9" height="600.4">
|
||||
</rect>
|
||||
<linearGradient id="polygon12_1_" gradientUnits="userSpaceOnUse" x1="1014.7582" y1="90.2012" x2="1077.5918" y2="356.7023" gradientTransform="matrix(0.9556 0.295 -0.2974 0.9605 -400.3649 -336.724)">
|
||||
<stop offset="4.835800e-02" style="stop-color:#9FA0A0"/>
|
||||
<stop offset="0.5227" style="stop-color:#D7D8D8;stop-opacity:0.4381"/>
|
||||
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<polygon id="polygon12" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st1" points="608.7,371.9
|
||||
511.9,134.7 490.9,134.1 430.7,377.1 ">
|
||||
</polygon>
|
||||
<linearGradient id="polygon21_1_" gradientUnits="userSpaceOnUse" x1="197.9478" y1="434.8972" x2="282.0578" y2="791.6389" gradientTransform="matrix(0.9983 -5.887031e-02 5.887031e-02 0.9983 28.0536 -430.7623)">
|
||||
<stop offset="4.835800e-02" style="stop-color:#898989"/>
|
||||
<stop offset="0.5874" style="stop-color:#D7D7D7;stop-opacity:0.3616"/>
|
||||
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<polygon id="polygon21" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st2" points="325.4,155.1
|
||||
289.2,163.9 291.9,437 480.9,450.4 ">
|
||||
</polygon>
|
||||
<radialGradient id="circle42_1_" cx="836.3" cy="506.5986" r="65.7125" gradientTransform="matrix(1 0 0 1 12 -19.0997)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFFFFF"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</radialGradient>
|
||||
<circle id="circle42" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st3" cx="848.3" cy="487.5" r="68.3">
|
||||
</circle>
|
||||
<linearGradient id="polygon58_1_" gradientUnits="userSpaceOnUse" x1="1863.538" y1="415.4688" x2="1929.7196" y2="696.1702" gradientTransform="matrix(0.8607 0.5092 -0.5092 0.8607 -635.7186 -1225.6498)">
|
||||
<stop offset="4.835800e-02" style="stop-color:#898989"/>
|
||||
<stop offset="0.5874" style="stop-color:#D7D7D7;stop-opacity:0.3616"/>
|
||||
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<polygon id="polygon58" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st4" points="547.4,383
|
||||
763.1,428.2 713.7,163.4 683.8,156 ">
|
||||
</polygon>
|
||||
<g id="g94" transform="rotate(9.0573675,796.06564,263.99283)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<path id="path60" inkscape:connector-curvature="0" class="st5" d="M520,160.2l-75.8,10.4c-5.6,0.8-10.8-3.2-11.5-8.7l0,0
|
||||
c-0.8-5.6,3.2-10.8,8.7-11.5l75.8-10.4c5.6-0.8,10.8,3.2,11.5,8.7l0,0C529.5,154.3,525.6,159.4,520,160.2L520,160.2z"/>
|
||||
|
||||
<ellipse id="circle62" transform="matrix(0.1574 -0.9875 0.9875 0.1574 220.2397 577.3303)" class="st6" cx="448.4" cy="159.6" rx="5.6" ry="5.6"/>
|
||||
|
||||
<ellipse id="circle64" transform="matrix(0.1574 -0.9875 0.9875 0.1574 241.8959 596.7076)" class="st6" cx="470.6" cy="156.6" rx="5.6" ry="5.6"/>
|
||||
|
||||
<ellipse id="circle66" transform="matrix(0.1574 -0.9875 0.9875 0.1574 304.0202 547.2599)" class="st7" cx="472.7" cy="95.5" rx="3.9" ry="3.9"/>
|
||||
|
||||
<ellipse id="circle68" transform="matrix(0.1574 -0.9875 0.9875 0.1574 263.9857 616.4264)" class="st6" cx="493.2" cy="153.5" rx="5.6" ry="5.6"/>
|
||||
|
||||
<ellipse id="circle70" transform="matrix(0.1574 -0.9875 0.9875 0.1574 285.6079 635.2634)" class="st6" cx="515.1" cy="150.3" rx="5.6" ry="5.6"/>
|
||||
<path id="path72" inkscape:connector-curvature="0" class="st8" d="M510,139.5l-61.3,8.4l-0.4-2.8c-0.2-1.5,0.8-2.9,2.3-3.1
|
||||
l55.7-7.7c1.5-0.2,2.9,0.8,3.1,2.3L510,139.5z"/>
|
||||
<path id="path74" inkscape:connector-curvature="0" class="st8" d="M519.9,161.7L444.9,172l0.3,2.4c0.3,1.7,1.8,2.8,3.4,2.6
|
||||
l69.1-9.5c1.7-0.3,2.8-1.8,2.6-3.4L519.9,161.7z"/>
|
||||
<path id="path76" inkscape:connector-curvature="0" class="st7" d="M508.2,170.4l-50.5,6.9l0.4,3c0.2,1.3,1.5,2.3,2.8,2.2l45.5-6.3
|
||||
c1.3-0.2,2.3-1.5,2.2-2.8L508.2,170.4z"/>
|
||||
<path id="path78" inkscape:connector-curvature="0" class="st7" d="M499.5,133.7l-43.7,6l-1.8-13.2c-1-7.3,4.1-14,11.3-15l17.3-2.4
|
||||
c7.3-1,14,4.1,15,11.3L499.5,133.7z"/>
|
||||
<line id="line80" class="st7" x1="473.5" y1="100.6" x2="474.6" y2="109.1"/>
|
||||
<line id="line82" class="st7" x1="471.6" y1="119.4" x2="464.5" y2="131.7"/>
|
||||
<line id="line84" class="st7" x1="485.9" y1="117.9" x2="478.7" y2="130.2"/>
|
||||
<path id="path86" inkscape:connector-curvature="0" class="st8" d="M470.7,182.8L467,193c-0.2,0.5,0.5,0.9,0.8,0.5l10.5-11.3
|
||||
c0.3-0.3,0-0.9-0.4-0.8l-6.8,1C471,182.5,470.8,182.6,470.7,182.8z"/>
|
||||
<path id="path88" inkscape:connector-curvature="0" class="st8" d="M460.7,184l-6.3,9.2c-0.3,0.5,0.3,1,0.8,0.7l12.8-10.1
|
||||
c0.4-0.3,0.2-0.9-0.4-0.9l-6.6,0.9C460.9,183.9,460.8,184,460.7,184L460.7,184z"/>
|
||||
<path id="path90" inkscape:connector-curvature="0" class="st8" d="M496.6,179l6.3,8.8c0.3,0.4-0.2,1-0.7,0.8l-13.2-8
|
||||
c-0.4-0.3-0.3-0.8,0.2-0.9l6.8-0.8C496.3,178.8,496.5,178.9,496.6,179L496.6,179z"/>
|
||||
<path id="path92" inkscape:connector-curvature="0" class="st8" d="M506.7,177.5l8.5,7.2c0.4,0.3,0,1.1-0.5,0.8l-15.1-6.3
|
||||
c-0.5-0.2-0.4-0.9,0.1-0.9l6.6-0.9C506.4,177.4,506.5,177.4,506.7,177.5L506.7,177.5z"/>
|
||||
</g>
|
||||
<g id="g130" transform="translate(-131.09867,-443.26745)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<path id="path96" inkscape:connector-curvature="0" class="st9" d="M875,594.1l-76.7-13.2c-5.6-0.9-9.4-6.4-8.5-12l0,0
|
||||
c0.9-5.6,6.4-9.4,12-8.5l76.7,13.2c5.6,0.9,9.4,6.4,8.5,12l0,0C885.9,591.3,880.6,595.1,875,594.1z"/>
|
||||
<circle id="circle98" class="st10" cx="805.6" cy="571.5" r="5.7"/>
|
||||
<circle id="circle100" class="st10" cx="828.2" cy="575.4" r="5.7"/>
|
||||
<circle id="circle102" class="st11" cx="849" cy="516.8" r="4"/>
|
||||
<circle id="circle104" class="st10" cx="851" cy="579.3" r="5.7"/>
|
||||
<circle id="circle106" class="st10" cx="873.2" cy="583" r="5.7"/>
|
||||
<path id="path108" inkscape:connector-curvature="0" class="st12" d="M871.6,570.9l-61.9-10.7l0.5-2.8c0.3-1.5,1.7-2.5,3.2-2.3
|
||||
l56.4,9.8c1.5,0.3,2.5,1.7,2.3,3.2L871.6,570.9z"/>
|
||||
<path id="path110" inkscape:connector-curvature="0" class="st12" d="M874.4,595.5l-76-13.2l-0.4,2.5c-0.3,1.7,0.8,3.3,2.5,3.6
|
||||
l69.8,12c1.7,0.3,3.3-0.8,3.6-2.5L874.4,595.5z"/>
|
||||
<path id="path112" inkscape:connector-curvature="0" class="st13" d="M860.3,600.3l-51.1-8.8l-0.5,3.1c-0.3,1.4,0.7,2.7,2.1,3l46,8
|
||||
c1.4,0.3,2.7-0.7,3-2.1L860.3,600.3z"/>
|
||||
<path id="path114" inkscape:connector-curvature="0" class="st11" d="M863.2,562.2l-44.2-7.6l2.3-13.3c1.3-7.3,8.3-12.3,15.6-11
|
||||
l17.5,3.1c7.3,1.3,12.3,8.3,11,15.6L863.2,562.2z"/>
|
||||
<line id="line116" class="st11" x1="848.1" y1="522.1" x2="846.6" y2="530.6"/>
|
||||
<line id="line118" class="st11" x1="840.5" y1="539.7" x2="829.8" y2="549.5"/>
|
||||
<line id="line120" class="st11" x1="854.8" y1="542.6" x2="844.2" y2="552.3"/>
|
||||
<path id="path122" inkscape:connector-curvature="0" class="st12" d="M820.1,600.8l-6.8,8.7c-0.3,0.4,0.2,1,0.7,0.8l13.7-7.6
|
||||
c0.4-0.3,0.3-0.8-0.2-0.9l-7-1.1C820.5,600.6,820.3,600.7,820.1,600.8L820.1,600.8z"/>
|
||||
<path id="path124" inkscape:connector-curvature="0" class="st12" d="M810,599l-8.9,7c-0.4,0.3-0.1,1.1,0.5,0.8l15.5-5.9
|
||||
c0.5-0.2,0.4-0.8-0.1-1l-6.6-1.2C810.3,598.8,810.1,598.9,810,599L810,599z"/>
|
||||
<path id="path126" inkscape:connector-curvature="0" class="st12" d="M846.4,605.1l3.5,10.5c0.2,0.5-0.5,0.9-0.8,0.5l-10.4-11.8
|
||||
c-0.3-0.3,0-0.9,0.5-0.8l6.9,1.3C846.3,604.9,846.4,605,846.4,605.1L846.4,605.1z"/>
|
||||
<path id="path128" inkscape:connector-curvature="0" class="st12" d="M856.6,606.8l6,9.6c0.3,0.5-0.3,1-0.8,0.7l-12.7-10.7
|
||||
c-0.4-0.3-0.1-1,0.4-0.8l6.6,1.2C856.4,606.6,856.5,606.7,856.6,606.8L856.6,606.8z"/>
|
||||
</g>
|
||||
<g id="g166" transform="translate(6.564267,-535.67492)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<path id="path132" inkscape:connector-curvature="0" class="st14" d="M331.5,670.9l-70.2,18.3c-5.1,1.3-10.4-1.8-11.7-6.9l0,0
|
||||
c-1.3-5.1,1.8-10.4,6.9-11.7l70.2-18.3c5.1-1.3,10.4,1.8,11.7,6.9l0,0C339.7,664.2,336.7,669.6,331.5,670.9z"/>
|
||||
<circle id="circle134" class="st15" cx="264.1" cy="678.3" r="5.3"/>
|
||||
<circle id="circle136" class="st15" cx="284.7" cy="673" r="5.3"/>
|
||||
<circle id="circle138" class="st16" cx="279.7" cy="615.2" r="3.7"/>
|
||||
<circle id="circle140" class="st15" cx="305.5" cy="667.5" r="5.3"/>
|
||||
<circle id="circle142" class="st15" cx="325.8" cy="662.1" r="5.3"/>
|
||||
<path id="path144" inkscape:connector-curvature="0" class="st17" d="M319.7,652.5L263,667.3l-0.7-2.5c-0.4-1.4,0.5-2.8,1.9-3.2
|
||||
l51.6-13.4c1.4-0.4,2.8,0.5,3.2,1.9L319.7,652.5z"/>
|
||||
<path id="path146" inkscape:connector-curvature="0" class="st17" d="M331.5,672.3L262,690.4l0.6,2.2c0.4,1.6,2,2.5,3.5,2.1
|
||||
l63.9-16.7c1.6-0.4,2.5-2,2.1-3.5L331.5,672.3z"/>
|
||||
<path id="path148" inkscape:connector-curvature="0" class="st13" d="M321.5,681.8L274.8,694l0.7,2.8c0.4,1.3,1.7,2.1,2.9,1.7
|
||||
l42.1-11c1.3-0.4,2.1-1.7,1.7-2.9L321.5,681.8z"/>
|
||||
<path id="path150" inkscape:connector-curvature="0" class="st16" d="M309.3,648.2l-40.5,10.5l-3.2-12.2c-1.8-6.7,2.3-13.6,9-15.4
|
||||
l16-4.2c6.7-1.8,13.6,2.3,15.4,9L309.3,648.2z"/>
|
||||
<line id="line152" class="st16" x1="281" y1="620.1" x2="283.1" y2="627.9"/>
|
||||
<line id="line154" class="st16" x1="281.4" y1="637.9" x2="276.1" y2="650.3"/>
|
||||
<line id="line156" class="st16" x1="294.6" y1="634.9" x2="289.3" y2="647.3"/>
|
||||
<path id="path158" inkscape:connector-curvature="0" class="st17" d="M287.6,697.6l-2.3,10.1c-0.1,0.5,0.6,0.8,0.8,0.4l8.7-11.7
|
||||
c0.3-0.4-0.1-0.8-0.5-0.7l-6.3,1.8C287.8,697.3,287.6,697.4,287.6,697.6L287.6,697.6z"/>
|
||||
<path id="path160" inkscape:connector-curvature="0" class="st17" d="M278.3,699.9l-4.8,9.3c-0.3,0.5,0.4,0.9,0.7,0.6l10.9-10.9
|
||||
c0.4-0.4,0-0.9-0.5-0.8l-6.1,1.6C278.5,699.8,278.4,699.8,278.3,699.9L278.3,699.9z"/>
|
||||
<path id="path162" inkscape:connector-curvature="0" class="st17" d="M311.6,691.2l7,7.6c0.4,0.4-0.1,0.9-0.6,0.7l-13.3-6.1
|
||||
c-0.4-0.2-0.4-0.7,0.1-0.9l6.3-1.6C311.3,691,311.5,691.1,311.6,691.2z"/>
|
||||
<path id="path164" inkscape:connector-curvature="0" class="st17" d="M320.8,688.7l8.8,5.8c0.5,0.3,0.1,1-0.4,0.8l-14.9-4.2
|
||||
c-0.5-0.1-0.5-0.8,0-0.9l6.1-1.6C320.6,688.6,320.7,688.6,320.8,688.7z"/>
|
||||
</g>
|
||||
<path id="path168" inkscape:connector-curvature="0" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st18" d="
|
||||
M241.1,524.1h-1c0.4-1.2,0.6-2.4,0.6-3.7c0-7-5.7-12.7-12.7-12.7h-3.3c-2.7,0-5.2,0.9-7.2,2.3c-1.2-0.8-2.7-1.3-4.3-1.3
|
||||
c-0.7,0-1.4,0.1-2.1,0.3c-0.1-3.9-3.3-7.1-7.2-7.1h-0.3c-2.2,0-4.1,1-5.4,2.5l0,0c2.7-3.8,4.4-8.5,4.4-13.6
|
||||
c0-13-10.6-23.5-23.5-23.5c-13,0-23.5,10.6-23.5,23.5c0,0.7,0,1.3,0.1,1.9c-2.9,0.3-5.7,1.3-8,2.9c-3.2-4.3-8.2-7.1-13.9-7.1l0,0
|
||||
c-9.5,0-17.3,7.8-17.3,17.3c0,0.1,0,0.3,0,0.4c-1.8-0.9-3.7-1.5-5.9-1.5c-6.9,0-12.6,5.7-12.6,12.6c0,0.7,0.1,1.5,0.2,2.2H92
|
||||
c-3,0-5.5,1.9-6.5,4.5h-9.9c-2.5,0-4.5,2-4.5,4.5s2,4.5,4.5,4.5h149.2h3.3h13.1c2.5,0,4.5-2,4.5-4.5
|
||||
C245.6,526.1,243.6,524.1,241.1,524.1L241.1,524.1z"/>
|
||||
<path id="path170" inkscape:connector-curvature="0" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st18" d="
|
||||
M898.4,516.3h-0.9c0.3-1,0.5-2.1,0.5-3.3c0-6.2-5.1-11.3-11.3-11.3h-2.9c-2.4,0-4.6,0.8-6.4,2c-1.1-0.7-2.4-1.1-3.8-1.1
|
||||
c-0.6,0-1.2,0.1-1.8,0.3c-0.1-3.5-2.9-6.3-6.4-6.3H865c-1.9,0-3.6,0.9-4.8,2.2l0,0c2.4-3.4,3.9-7.6,3.9-12
|
||||
c0-11.5-9.4-20.9-20.9-20.9s-20.9,9.4-20.9,20.9c0,0.6,0,1.2,0.1,1.7c-2.6,0.2-5,1.2-7.1,2.6c-2.8-3.8-7.3-6.3-12.3-6.3l0,0
|
||||
c-8.5,0-15.4,6.9-15.4,15.4c0,0.1,0,0.2,0,0.4c-1.6-0.8-3.3-1.4-5.2-1.4c-6.2,0-11.2,5-11.2,11.2c0,0.7,0.1,1.3,0.2,1.9h-5.5
|
||||
c-2.6,0-4.9,1.7-5.8,4h-8.8c-2.2,0-4,1.8-4,4s1.8,4,4,4h132.5h2.9h11.6c2.2,0,4-1.8,4-4C902.4,518.1,900.6,516.3,898.4,516.3
|
||||
L898.4,516.3z"/>
|
||||
<g id="g184" transform="translate(10.641067,-115.56078)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<g id="g178">
|
||||
<path id="path172" inkscape:connector-curvature="0" class="st19" d="M149.5,586c2.4,4.6,6.2,8.3,10.8,10.8c3.6,1.9,7.6,3,11.9,3
|
||||
c14.2,0,25.7-11.5,25.7-25.7c0-4.3-1.1-8.4-3-11.9c-2.4-4.6-6.2-8.3-10.8-10.8c-3.6-1.9-7.6-3-11.9-3c-14.2,0-25.7,11.5-25.7,25.7
|
||||
C146.6,578.4,147.7,582.4,149.5,586z"/>
|
||||
<path id="path174" inkscape:connector-curvature="0" class="st18" d="M194.1,562.5c-2.3-4.4-6-8.1-10.4-10.4
|
||||
c-3.4-1.8-7.4-2.9-11.5-2.9c-9.5,0-17.7,5.3-21.8,13.1c4-2.8,8.9-4.4,14.1-4.4c4.2,0,8.1,1,11.5,2.9c4.4,2.3,8.1,6,10.4,10.4
|
||||
c1.8,3.4,2.9,7.4,2.9,11.5c0,4.2-1.1,8.2-2.9,11.7c6.4-4.5,10.6-11.9,10.6-20.3C197,569.8,196,565.9,194.1,562.5L194.1,562.5z"/>
|
||||
<path id="path176" inkscape:connector-curvature="0" class="st20" d="M149.5,586c-7.7,10-11.6,17.8-9.3,20.1s10.1-1.6,20.1-9.3
|
||||
c5.6-4.4,12-9.9,18.3-16.3c6.4-6.4,11.9-12.7,16.3-18.3c7.7-10,11.6-17.8,9.3-20.1s-10.1,1.6-20.1,9.3"/>
|
||||
</g>
|
||||
<path id="path180" inkscape:connector-curvature="0" class="st21" d="M154,566.3c0.5-1.2,1.1-2.3,1.8-3.4c0.7-1.1,1.5-2,2.4-2.9
|
||||
s1.9-1.7,2.9-2.4c1.1-0.7,2.2-1.3,3.4-1.8s2.4-0.9,3.7-1.2s2.6-0.4,4-0.4"/>
|
||||
<path id="path182" inkscape:connector-curvature="0" class="st21" d="M152.4,574.1c0-1.4,0.1-2.7,0.4-4"/>
|
||||
</g>
|
||||
<g id="g192" transform="translate(-0.2304,235.22748)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<polygon id="polygon186" class="st19" points="843.4,216.1 850,204.1 856.6,216.1 868.7,222.7 856.6,229.3 850,241.4 843.4,229.3
|
||||
831.4,222.7 "/>
|
||||
<polygon id="polygon188" class="st19" points="868.4,248.1 873.4,239.1 878.3,248.1 887.4,253.1 878.3,258 873.4,267.1 868.4,258
|
||||
859.4,253.1 "/>
|
||||
<polygon id="polygon190" class="st19" points="884.1,207.8 887.4,201.7 890.7,207.8 896.7,211.1 890.7,214.4 887.4,220.4
|
||||
884.1,214.4 878,211.1 "/>
|
||||
</g>
|
||||
<g inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st22">
|
||||
<path class="st23" d="M332.8,346.5l65.5-89.9h24.1v89.6h17.5v20.8h-17.5v28.8h-27.9v-28.8h-61.7V346.5z M358.9,346.1h35.6v-34.4
|
||||
c0-5.9,0.2-11.6,0.8-17h-0.9c-4.9,7.8-8,12.7-9.4,14.6L358.9,346.1z"/>
|
||||
<path class="st23" d="M450.4,326.2c0-11.1,1.1-21,3.2-29.6c2.1-8.6,4.9-15.5,8.2-20.7c3.3-5.2,7.3-9.4,11.9-12.8
|
||||
c4.6-3.3,9.1-5.6,13.5-6.8c4.4-1.2,9-1.8,13.7-1.8c16.3,0,28.8,6.4,37.5,19.1c8.7,12.7,13.1,30.3,13.1,52.6
|
||||
c0,22.1-4.4,39.6-13.1,52.4c-8.7,12.8-21.2,19.2-37.4,19.2c-4.5,0-8.9-0.6-13.2-1.7c-4.3-1.1-8.7-3.3-13.4-6.6
|
||||
c-4.6-3.2-8.7-7.4-12.1-12.5c-3.5-5.1-6.3-12-8.6-20.8C451.5,347.6,450.4,337.6,450.4,326.2z M479.4,326.2
|
||||
c0,33.8,7.2,50.7,21.6,50.7c14.2,0,21.3-16.9,21.3-50.7c0-33.8-7.2-50.7-21.5-50.7C486.5,275.5,479.4,292.4,479.4,326.2z"/>
|
||||
<path class="st23" d="M562.4,346.5l65.5-89.9h24.1v89.6h17.5v20.8h-17.5v28.8h-27.8v-28.8h-61.7V346.5z M588.5,346.1h35.6v-34.4
|
||||
c0-5.9,0.2-11.6,0.8-17h-0.9c-4.9,7.8-8,12.7-9.4,14.6L588.5,346.1z"/>
|
||||
</g>
|
||||
<g inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
|
||||
<path class="st23" d="M435.8,197.2c0-6.4,1.6-11.6,4.9-15.5c3.2-3.9,7.5-5.9,12.8-5.9c5.3,0,9.6,2,12.8,5.9
|
||||
c3.2,3.9,4.8,9.1,4.8,15.5c0,6.4-1.6,11.6-4.8,15.4c-3.2,3.9-7.5,5.8-12.8,5.8c-5.3,0-9.6-1.9-12.8-5.8
|
||||
C437.5,208.8,435.8,203.6,435.8,197.2z M443.6,197.2c0,4.6,0.9,8.2,2.6,10.8c1.7,2.7,4.2,4,7.3,4c3.1,0,5.5-1.3,7.2-4
|
||||
c1.7-2.7,2.6-6.3,2.6-10.9c0-4.6-0.9-8.2-2.6-10.8c-1.7-2.7-4.1-4-7.2-4c-3.1,0-5.5,1.3-7.2,4C444.5,189,443.6,192.7,443.6,197.2z"
|
||||
/>
|
||||
<path class="st23" d="M474.8,202.7c0-4.4,1.2-8.2,3.5-11.2c2.3-3.1,5.8-4.6,10.4-4.6c3.1,0,5.7,0.8,7.9,2.3
|
||||
c2.2,1.6,3.7,3.5,4.6,5.8c0.9,2.3,1.4,4.9,1.4,7.7c0,1.4-0.1,2.7-0.4,4c-0.2,1.3-0.7,2.7-1.3,4.2c-0.7,1.5-1.5,2.7-2.5,3.8
|
||||
c-1,1.1-2.3,2-4,2.7c-1.7,0.7-3.6,1.1-5.7,1.1c-2.1,0-4-0.3-5.6-1c-1.7-0.7-3-1.5-4-2.6c-1-1.1-1.8-2.3-2.5-3.7
|
||||
c-0.7-1.4-1.1-2.8-1.4-4.2C475,205.6,474.8,204.1,474.8,202.7z M482.3,202.7c0,3.4,0.6,5.8,1.9,7.3c1.3,1.5,2.8,2.3,4.5,2.3
|
||||
c1.7,0,3.1-0.8,4.5-2.3c1.3-1.5,2-4,2-7.3c0-3.4-0.7-5.8-2-7.4c-1.3-1.5-2.8-2.3-4.5-2.3c-1.7,0-3.2,0.8-4.5,2.3
|
||||
C482.9,196.8,482.3,199.3,482.3,202.7z"/>
|
||||
<path class="st23" d="M507.9,229.5v-41.8h6.9v3.3c1.9-2.8,4.3-4.3,7.4-4.3c3.6,0,6.7,1.4,9.1,4.3c2.4,2.8,3.6,6.7,3.6,11.5
|
||||
c0,2.8-0.4,5.2-1.2,7.3c-0.8,2.1-1.8,3.8-3.1,5c-1.3,1.2-2.6,2.1-4.1,2.7c-1.4,0.6-2.9,0.9-4.4,0.9c-1.8,0-3.3-0.4-4.5-1.2
|
||||
c-1.2-0.8-2.1-1.7-2.7-2.7v14.9H507.9z M515,202.6c0,3,0.5,5.3,1.6,7.1c1.1,1.7,2.6,2.6,4.6,2.6c1.9,0,3.4-0.8,4.6-2.5
|
||||
c1.1-1.7,1.7-4.1,1.7-7.2c0-3-0.6-5.3-1.7-7c-1.1-1.7-2.7-2.6-4.6-2.6c-2,0-3.5,0.9-4.6,2.7C515.6,197.4,515,199.8,515,202.6z"/>
|
||||
<path class="st23" d="M537.8,211.3l5.1-3c2,3,4.6,4.5,7.9,4.5c1.5,0,2.6-0.3,3.4-0.9c0.8-0.6,1.2-1.4,1.2-2.3c0-0.3,0-0.6-0.1-0.8
|
||||
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.4-0.6-0.6c-0.2-0.2-0.4-0.4-0.8-0.6c-0.4-0.2-0.7-0.4-0.9-0.5c-0.2-0.1-0.6-0.3-1.1-0.5
|
||||
c-0.5-0.2-0.9-0.3-1.2-0.4c-0.3-0.1-0.7-0.2-1.3-0.4c-0.6-0.2-1-0.3-1.3-0.4c-2.6-0.8-4.6-1.9-6.2-3.2c-1.6-1.3-2.3-3.2-2.3-5.7
|
||||
c0-2.7,1.1-4.8,3.4-6.4c2.3-1.6,5-2.4,8.3-2.4c2.5,0,4.8,0.6,7,1.9c2.2,1.3,3.8,3,4.9,5l-4.8,2.9c-2.1-2.7-4.5-4-7.1-4
|
||||
c-1.4,0-2.5,0.3-3.2,0.8c-0.8,0.6-1.1,1.3-1.1,2.2c0,0.3,0,0.6,0.1,0.9c0.1,0.3,0.2,0.5,0.4,0.8c0.2,0.2,0.4,0.4,0.6,0.6
|
||||
c0.2,0.2,0.5,0.4,0.8,0.6c0.4,0.2,0.7,0.4,0.9,0.5c0.3,0.1,0.6,0.3,1.1,0.4c0.5,0.2,0.8,0.3,1.1,0.4c0.3,0.1,0.7,0.2,1.2,0.4
|
||||
c0.5,0.2,0.9,0.3,1.2,0.4c5.9,2,8.9,4.9,8.9,8.9c0,2.5-1,4.6-3.1,6.4c-2.1,1.7-5,2.6-8.8,2.6c-2.9,0-5.6-0.7-7.9-2
|
||||
S539.1,213.4,537.8,211.3z"/>
|
||||
<path class="st23" d="M568.2,217.7v-8.3h9.3v8.3H568.2z M576.4,205.9h-7l-0.5-29.4h8L576.4,205.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st23" d="M333.4,464.8v-27.1h12c3.2,0,5.7,0.8,7.5,2.3c1.8,1.5,2.7,3.6,2.7,6.1c0,2.6-0.9,4.6-2.8,6.1
|
||||
s-4.3,2.2-7.4,2.2H339v10.3H333.4z M339,450.2h5.7c1.6,0,2.9-0.4,3.8-1.1c0.9-0.7,1.4-1.7,1.4-3c0-1.3-0.4-2.3-1.3-3
|
||||
c-0.9-0.7-2.2-1.1-3.8-1.1H339V450.2z"/>
|
||||
<path class="st23" d="M354.1,464.8l10.8-27.1h6l10.8,27.1h-5.9l-2.3-6.4h-11.2l-2.3,6.4H354.1z M363.7,454.4h8.3l-2.2-6.4
|
||||
c-0.5-1.5-1.1-3.3-1.8-5.5h-0.2c-0.2,0.7-0.5,1.6-0.9,2.8c-0.4,1.2-0.7,2.1-0.9,2.6L363.7,454.4z"/>
|
||||
<path class="st23" d="M383,451.2c0-4.1,1.3-7.5,3.8-10.2c2.5-2.6,5.9-4,10-4c1.3,0,2.5,0.2,3.7,0.5c1.2,0.3,2.1,0.7,2.9,1.2
|
||||
c0.8,0.5,1.5,1,2.2,1.7c0.7,0.6,1.2,1.2,1.5,1.7c0.4,0.5,0.7,1,0.9,1.5l-4.8,1.4c-0.4-0.5-0.8-1-1.1-1.3c-0.3-0.3-0.7-0.7-1.2-1.2
|
||||
s-1.1-0.8-1.8-1s-1.4-0.3-2.3-0.3c-2.5,0-4.5,0.9-5.9,2.7c-1.4,1.8-2.1,4.2-2.1,7.2c0,3,0.7,5.4,2.2,7.2c1.5,1.8,3.3,2.7,5.6,2.7
|
||||
c2,0,3.5-0.5,4.6-1.4c1.1-1,1.8-2.2,2-3.8c0.1-1.3,0.2-2,0.2-2.1h-7.1v-4.3h12.5v15.3H405l-0.5-2.4c-1.7,2-4.4,3-8,3
|
||||
c-3.8,0-7-1.3-9.6-3.8C384.3,459,383,455.5,383,451.2z"/>
|
||||
<path class="st23" d="M413.9,464.8v-27.1H434v4.3h-14.6v6.7h13.1v4.2h-13.1v7.6h14.9v4.3H413.9z"/>
|
||||
<path class="st23" d="M448.1,464.8v-27.1h5.8l8.5,14.1l3.3,5.5h0.2c-0.4-2.6-0.6-5.2-0.6-7.9v-11.7h5.6v27.1h-5.8l-8.5-13.8
|
||||
l-3.3-5.7h-0.2c0.4,2.5,0.6,5.1,0.6,7.9v11.6H448.1z"/>
|
||||
<path class="st23" d="M475.4,451.3c0-4.3,1.2-7.7,3.6-10.3c2.4-2.6,5.6-3.9,9.5-3.9c4,0,7.1,1.3,9.5,3.9c2.4,2.6,3.6,6,3.6,10.3
|
||||
c0,4.3-1.2,7.7-3.6,10.3c-2.4,2.6-5.6,3.9-9.5,3.9s-7.1-1.3-9.5-3.9C476.6,458.9,475.4,455.5,475.4,451.3z M481.2,451.3
|
||||
c0,3,0.6,5.4,1.9,7.2c1.3,1.8,3.1,2.7,5.4,2.7c2.3,0,4.1-0.9,5.4-2.6c1.3-1.8,1.9-4.2,1.9-7.2c0-3-0.6-5.4-1.9-7.2
|
||||
c-1.3-1.8-3.1-2.7-5.4-2.7c-2.3,0-4.1,0.9-5.4,2.7C481.9,445.8,481.2,448.2,481.2,451.3z"/>
|
||||
<path class="st23" d="M502.7,442v-4.3h22.7v4.3h-8.6v22.8h-5.6V442H502.7z"/>
|
||||
<path class="st23" d="M538.3,464.8v-27.1h19.6v4.3h-13.9v7.1h12.5v4.2h-12.5v11.4H538.3z"/>
|
||||
<path class="st23" d="M559.9,451.3c0-4.3,1.2-7.7,3.6-10.3c2.4-2.6,5.6-3.9,9.5-3.9c4,0,7.1,1.3,9.5,3.9c2.4,2.6,3.6,6,3.6,10.3
|
||||
c0,4.3-1.2,7.7-3.6,10.3c-2.4,2.6-5.6,3.9-9.5,3.9c-4,0-7.1-1.3-9.5-3.9C561.1,458.9,559.9,455.5,559.9,451.3z M565.7,451.3
|
||||
c0,3,0.6,5.4,1.9,7.2c1.3,1.8,3.1,2.7,5.4,2.7c2.3,0,4.1-0.9,5.4-2.6c1.3-1.8,1.9-4.2,1.9-7.2c0-3-0.6-5.4-1.9-7.2
|
||||
c-1.3-1.8-3.1-2.7-5.4-2.7c-2.3,0-4.1,0.9-5.4,2.7C566.3,445.8,565.7,448.2,565.7,451.3z"/>
|
||||
<path class="st23" d="M590.5,454.6v-16.9h5.6v16.9c0,4.4,1.9,6.5,5.7,6.5c3.8,0,5.7-2.2,5.7-6.5v-16.9h5.6v16.9
|
||||
c0,3.5-0.9,6.2-2.8,8c-1.9,1.9-4.7,2.8-8.5,2.8c-3.6,0-6.4-0.9-8.4-2.7S590.5,458.2,590.5,454.6z"/>
|
||||
<path class="st23" d="M619.3,464.8v-27.1h5.8l8.5,14.1l3.3,5.5h0.2c-0.4-2.6-0.6-5.2-0.6-7.9v-11.7h5.6v27.1h-5.8l-8.5-13.8
|
||||
l-3.3-5.7h-0.2c0.4,2.5,0.6,5.1,0.6,7.9v11.6H619.3z"/>
|
||||
<path class="st23" d="M648.3,464.8v-27.1h9.2c4.6,0,8.2,1.2,10.7,3.5s3.7,5.7,3.7,10c0,1.4-0.1,2.8-0.4,4c-0.3,1.3-0.8,2.5-1.4,3.7
|
||||
c-0.7,1.2-1.6,2.2-2.6,3c-1.1,0.8-2.4,1.5-4.1,2c-1.7,0.5-3.6,0.8-5.8,0.8H648.3z M653.9,460.5h3c3,0,5.3-0.7,6.8-2.2
|
||||
c1.5-1.5,2.3-3.8,2.3-7.1c0-3.3-0.8-5.7-2.4-7.2s-3.8-2.1-6.7-2.1h-3.1V460.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 22 KiB |
37
docs/_static/diag_frame.diag
vendored
Normal file
37
docs/_static/diag_frame.diag
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Modbus float_abcd frame structure diagram
|
||||
|
||||
blockdiag mb_float_frame {
|
||||
# global properties
|
||||
span_width = 5;
|
||||
span_height = 5;
|
||||
node_height = 30;
|
||||
default_fontsize = 15;
|
||||
default_group_color = lightgrey;
|
||||
class spacer [shape=none, width=10];
|
||||
# tuning node properties and connections
|
||||
0,1,2 [class=spacer];
|
||||
0; note
|
||||
1; header
|
||||
2; response -- uid -- cmd -- len -- fl_abcd -- crc
|
||||
group float_abcd_packet {
|
||||
label = "PDU";
|
||||
color = gray;
|
||||
shape = line;
|
||||
style = dashed;
|
||||
group{uid,resp_uid};group{cmd,resp_cmd};group{len,resp_len};group{crc,resp_crc};
|
||||
group float_abcd{
|
||||
color = blue;
|
||||
shape = line;
|
||||
style = dashed;
|
||||
fl_abcd;dt_abcd;
|
||||
}
|
||||
}
|
||||
note[label="1: Unit Identificator, 2: Function code, 3: Data length, 4: Float data array, 5: Checksum",colwidth=6,color=lightyellow,shape=roundedbox]
|
||||
header[label="FLOAT_ABCD = 0x4640e400 = 12345.0",colwidth=6,color=lightgreen]
|
||||
response[label="RX:",color=yellow];
|
||||
uid[label="UID",numbered=1];cmd[label="FC",numbered=2];
|
||||
len[label="LENGTH",numbered=3];crc[label="CRC",numbered=5];
|
||||
resp_uid[label="0x01"];resp_cmd[label="0x03"];resp_len[label="0x08"];resp_crc[label="0x9065"];
|
||||
fl_abcd[label="FLOAT_ABCD",color=lightgreen,numbered=4];
|
||||
dt_abcd[label="0xE4004640",shape=note];
|
||||
}
|
BIN
docs/_static/modbus-data-mapping.png
vendored
Normal file
BIN
docs/_static/modbus-data-mapping.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
docs/_static/modbus-segment.png
vendored
Normal file
BIN
docs/_static/modbus-segment.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
22
docs/_static/modbus_docs_versions.js
vendored
Normal file
22
docs/_static/modbus_docs_versions.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
var DOCUMENTATION_VERSIONS = {
|
||||
DEFAULTS: { has_targets: true,
|
||||
supported_targets: [ "esp32", "esp32s2", "esp32s3", "esp32c2","esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32s2", "esp32s3, esp32p4" ]
|
||||
},
|
||||
VERSIONS: [
|
||||
{ name: "latest" },
|
||||
{ name: "v1.0.1", old:true },
|
||||
{ name: "v2.0.0", old:false }
|
||||
],
|
||||
IDF_TARGETS: [
|
||||
{ text: "ESP32", value: "esp32"},
|
||||
{ text: "ESP32-S2", value: "esp32s2"},
|
||||
{ text: "ESP32-S3", value: "esp32s3"},
|
||||
{ text: "ESP32-C2", value: "esp32c2"},
|
||||
{ text: "ESP32-C3", value: "esp32c3"},
|
||||
{ text: "ESP32-C6", value: "esp32c6"},
|
||||
{ text: "ESP32-H2", value: "esp32h2"},
|
||||
{ text: "ESP32-P4", value: "esp32p4"},
|
||||
{ text: "ESP32-C5", value: "esp32c5"},
|
||||
{ text: "ESP32-C61", value: "esp32c61"}
|
||||
]
|
||||
};
|
61
docs/_static/modbus_frame_examples.diag
vendored
Normal file
61
docs/_static/modbus_frame_examples.diag
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# Modbus frame packaging examples
|
||||
|
||||
blockdiag mb_master_frames {
|
||||
# global properties
|
||||
span_width = 5;
|
||||
span_height = 5;
|
||||
node_height = 30;
|
||||
default_group_color = lightgrey;
|
||||
default_fontsize = 15;
|
||||
# tuning node properties and connections
|
||||
group 16bit_packets {
|
||||
label = "16bit frame";
|
||||
color = red;
|
||||
shape = line;
|
||||
style = dashed;
|
||||
16bit_notes;
|
||||
}
|
||||
group 32bit_packets {
|
||||
label = "32bit frame";
|
||||
color = green;
|
||||
shape = line;
|
||||
style = dashed;
|
||||
group{32bit_notes};
|
||||
}
|
||||
group 64bit_packets {
|
||||
label = "64bit frame";
|
||||
color = blue;
|
||||
shape = line;
|
||||
style = dashed;
|
||||
64bit_notes;
|
||||
}
|
||||
16bit_notes[label="(UINT16, INT16) VALUE = 0x3039 = 12345", width=600, color=orange, shape = roundedbox];
|
||||
req_u16_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=520, colwidth=2, shape = roundedbox ,group=16bit_packets];
|
||||
req_u16_frm1[label="TX:| 01 | 03 | 00 04 | 00 02 | 85 CA |", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
|
||||
rsp_u16_hd1[label= "RX:| UID | FC | LEN | UINT16_AB1 | UINT16_AB2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=16bit_packets];
|
||||
rsp_u16_frm1[label="RX:| 01 | 03 | 04 | 30 39 | 30 39 | F1 2C |", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
|
||||
rsp_u16_hd2[label= "RX:| UID | FC | LEN | UINT16_BA1 | UINT16_BA2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=16bit_packets];
|
||||
rsp_u16_frm2[label="RX:| 01 | 03 | 04 | 39 30 | 39 30 | E4 E4 |\n", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
|
||||
32bit_notes[label="(UINT32, INT32, FLOAT32) VALUE = 0x4640e400 = 12345.0", width=600, color=lightgreen, shape = roundedbox];
|
||||
req_fl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=32bit_packets];
|
||||
req_fl_frm1[label="TX:| 01 | 03 | 00 XX | 00 04 | C5 CB |", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
|
||||
rsp_fl_hd1[label= "RX:| UID | FC | LEN | FLOAT_ABCD1 | FLOAT_ABCD2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=32bit_packets];
|
||||
rsp_fl_frm1[label="RX:| 01 | 03 | 08 | E4 00 46 40 | E4 00 46 40 | 90 65 |", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
|
||||
rsp_fl_hd2[label= "RX:| UID | FC | LEN | FLOAT_CDAB1 | FLOAT_CDAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
|
||||
rsp_fl_frm2[label="RX:| 01 | 03 | 08 | 46 40 E4 00 | 46 40 E4 00 | 18 71 |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
|
||||
rsp_fl_hd3[label= "RX:| UID | FC | LEN | FLOAT_BADC1 | FLOAT_BADC2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
|
||||
rsp_fl_frm3[label="RX:| 01 | 03 | 08 | 00 E4 40 46 | 00 E4 40 46 | 46 D3 |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
|
||||
rsp_fl_hd4[label= "RX:| UID | FC | LEN | FLOAT_DCAB1 | FLOAT_DCAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
|
||||
rsp_fl_frm4[label="RX:| 01 | 03 | 08 | 40 46 00 E4 | 40 46 00 E4 | 32 6B |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
|
||||
64bit_notes[label="(UINT64, INT64, FLOAT64) VALUE = 0x40c81c8000000000 = 12345.0", width=600, color=lightblue, shape = roundedbox];
|
||||
req_dbl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=64bit_packets];
|
||||
req_dbl_frm1[label="TX:| 01 | 03 | 00 28 | 00 08 | C4 04 |", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
|
||||
rsp_dbl_hd1[label= "RX:| UID | FC | LEN | DOUBLE_ABCDEFGH1 | DOUBLE_ABCDEFGH2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=64bit_packets];
|
||||
rsp_dbl_frm1[label="RX:| 01 | 03 | 10 | 00 00 00 00 1C 80 40 C8 | 00 00 00 00 1C 80 40 C8 | 9F 4B |", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
|
||||
rsp_dbl_hd2[label= "RX:| UID | FC | LEN | DOUBLE_HGFEDCBA1 | DOUBLE_HGFEDCBA2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
|
||||
rsp_dbl_frm2[label="RX:| 01 | 03 | 10 | C8 40 80 1C 00 00 00 00 | C8 40 80 1C 00 00 00 00 | DF D3 |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
|
||||
rsp_dbl_hd3[label= "RX:| UID | FC | LEN | DOUBLE_GHEFCDAB1 | DOUBLE_GHEFCDAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
|
||||
rsp_dbl_frm3[label="RX:| 01 | 03 | 10 | 40 C8 1C 80 00 00 00 00 | 40 C8 1C 80 00 00 00 00 | B1 9C |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
|
||||
rsp_dbl_hd4[label= "RX:| UID | FC | LEN | DOUBLE_BADCFEHG1 | DOUBLE_BADCFEHG2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
|
||||
rsp_dbl_frm4[label="RX:| 01 | 03 | 10 | 00 00 00 00 80 1C C8 40 | 00 00 00 00 80 1C C8 40 | 86 94 |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
|
||||
}
|
33
docs/conf_common.py
Normal file
33
docs/conf_common.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Common (non-language-specific) configuration for Sphinx
|
||||
#
|
||||
|
||||
# type: ignore
|
||||
# pylint: disable=wildcard-import
|
||||
# pylint: disable=undefined-variable
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from esp_docs.conf_docs import * # noqa: F403,F401
|
||||
|
||||
extensions += ['sphinx_copybutton',
|
||||
# Needed as a trigger for running doxygen
|
||||
'esp_docs.esp_extensions.dummy_build_system',
|
||||
'esp_docs.esp_extensions.run_doxygen'
|
||||
]
|
||||
|
||||
# link roles config
|
||||
github_repo = 'espressif/esp-modbus'
|
||||
|
||||
# context used by sphinx_idf_theme
|
||||
html_context['github_user'] = 'espressif'
|
||||
html_context['github_repo'] = 'esp-modbus'
|
||||
html_static_path = ['../_static']
|
||||
|
||||
# Extra options required by sphinx_idf_theme
|
||||
project_slug = 'esp-modbus'
|
||||
versions_url = './_static/modbus_docs_versions.js'
|
||||
|
||||
idf_targets = [ 'esp32' ]
|
||||
languages = ['en']
|
78
docs/en/applications_and_references.rst
Normal file
78
docs/en/applications_and_references.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
Possible Communication Issues And Solutions
|
||||
-------------------------------------------
|
||||
|
||||
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:
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
::
|
||||
|
||||
E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT).
|
||||
|
||||
|
||||
.. list-table:: Table 5 Modbus error codes and troubleshooting
|
||||
:widths: 5 30 65
|
||||
:header-rows: 1
|
||||
|
||||
* - Error
|
||||
- Description
|
||||
- Possible solution
|
||||
* - 0x106
|
||||
- ``ESP_ERR_NOT_SUPPORTED`` - Invalid register request - slave returned an exception because the requested register is not supported.
|
||||
- Refer to slave register map. Check the master data dictionary for correctness.
|
||||
* - 0x107
|
||||
- ``ESP_ERR_TIMEOUT`` - Slave response timeout - Modbus slave did not send response during configured slave response timeout.
|
||||
- Measure and increase the maximum slave response timeout `idf.py menuconfig`, option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND``.
|
||||
Check physical connection or network configuration and make sure that the slave response can reach the master side.
|
||||
If the application has some high performance tasks with higher priority than ``CONFIG_FMB_PORT_TASK_PRIO`` it is recommended to place Modbus tasks on the other core using an option ``CONFIG_FMB_PORT_TASK_AFFINITY``.
|
||||
Configure the Modbus task's priority ``CONFIG_FMB_PORT_TASK_PRIO`` to ensure that the task gets sufficient processing time to handle Modbus stack events.
|
||||
* - 0x108
|
||||
- ``ESP_ERR_INVALID_RESPONSE`` - Received unsupported response from slave or frame check failure. Master can not execute command handler because the command is either not supported or is incorrect.
|
||||
- Check the physical connection then refer to register map of your slave to configure the master data dictionary properly.
|
||||
* - 0x103
|
||||
- ``ESP_ERR_INVALID_STATE`` - Critical failure or FSM sequence failure or master FSM is busy processing previous request.
|
||||
- Make sure your physical connection is working properly. Increase task stack size and check Modbus initialization sequence.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
The examples below demonstrate the library port for serial, TCP slave and master implementations accordingly. The selection of stack is performed through KConfig menu option "Enable Modbus stack support ..." for appropriate communication mode and related configuration keys.
|
||||
|
||||
.. _example_mb_slave:
|
||||
|
||||
- `Modbus serial slave example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_slave>`__
|
||||
|
||||
.. _example_mb_master:
|
||||
|
||||
- `Modbus serial master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_master>`__
|
||||
|
||||
.. _example_mb_tcp_master:
|
||||
|
||||
- `Modbus TCP master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_slave>`__
|
||||
|
||||
.. _example_mb_tcp_slave:
|
||||
|
||||
- `Modbus TCP slave example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_master>`__
|
||||
|
||||
Please refer to the specific example README.md for details.
|
||||
|
||||
.. _modbus_organization:
|
||||
|
||||
Protocol References
|
||||
-------------------
|
||||
|
||||
- `Modbus Organization with protocol specifications <https://modbus.org/specs.php>`__
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_modbus_common.inc
|
||||
.. include-build-file:: inc/esp_modbus_master.inc
|
||||
.. include-build-file:: inc/esp_modbus_slave.inc
|
||||
|
||||
.. _modbus_api_endianness_conversion:
|
||||
|
||||
Modbus Endianness Conversion API Reference
|
||||
------------------------------------------
|
||||
|
||||
.. include-build-file:: inc/mb_endianness_utils.inc
|
27
docs/en/conf.py
Normal file
27
docs/en/conf.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# English Language RTD & Sphinx config file
|
||||
#
|
||||
# Uses ../conf_common.py for most non-language-specific settings.
|
||||
|
||||
# Importing conf_common adds all the non-language-specific
|
||||
# parts to this conf module
|
||||
try:
|
||||
from conf_common import * # noqa: F403,F401
|
||||
except ImportError:
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../'))
|
||||
from conf_common import * # noqa: F403,F401
|
||||
|
||||
import datetime
|
||||
|
||||
current_year = datetime.datetime.now().year
|
||||
|
||||
# General information about the project.
|
||||
project = u'ESP-Modbus Programming Guide'
|
||||
copyright = u'2019 - {}, Espressif Systems (Shanghai) Co., Ltd'.format(current_year)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
16
docs/en/index.rst
Normal file
16
docs/en/index.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
ESP-Modbus Library
|
||||
==================
|
||||
|
||||
An Espressif ESP-Modbus Library (esp-modbus) is a library to support Modbus communication in the networks based on RS485 or Ethernet interfaces.
|
||||
The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs).
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
The Overview, Messaging Model And Data Mapping <overview_messaging_and_mapping>
|
||||
Modbus Port Initialization <port_initialization>
|
||||
Modbus Master API <master_api_overview>
|
||||
Modbus Slave API <slave_api_overview>
|
||||
Applications and References <applications_and_references>
|
||||
|
||||
.. note:: The ESP-Modbus library starting from version v2.0.0 supports creation of several instances of Modbus master and slave objects. The instance of each type shall be created using appropriate constructor API which uses the pointer to predefined object configuration options as first parameter and returns the pointer to created communication object as a second parameter. The returned pointer shall be saved and later be used as instance identifier in first parameter of each call of Modbus API functions.
|
415
docs/en/master_api_overview.rst
Normal file
415
docs/en/master_api_overview.rst
Normal file
@@ -0,0 +1,415 @@
|
||||
.. _modbus_api_master_overview:
|
||||
|
||||
Modbus Master API Overview
|
||||
--------------------------
|
||||
|
||||
The following overview describes how to setup Modbus master communication. The overview reflects a typical programming workflow and is broken down into the sections provided below:
|
||||
|
||||
1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface for the selected port.
|
||||
2. :ref:`modbus_api_master_configure_descriptor` - Configure data descriptors to access slave parameters.
|
||||
3. :ref:`modbus_api_master_setup_communication_options` - Allows to setup communication options for selected port.
|
||||
4. :ref:`modbus_api_master_start_communication` - Start stack and sending / receiving data.
|
||||
5. :ref:`modbus_api_master_destroy` - Destroy Modbus controller and its resources.
|
||||
|
||||
.. _modbus_api_master_configure_descriptor:
|
||||
|
||||
Configuring Master Data Access
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The architectural approach of ESP_Modbus includes one level above standard Modbus IO driver. The additional layer is called Modbus controller and its goal is to add an abstraction such as CID - characteristic identifier. The CID is linked to a corresponding Modbus registers through the table called Data Dictionary and represents device physical parameter (such as temperature, humidity, etc.) in specific Modbus slave device. This approach allows the upper layer (e.g., MESH or MQTT) to be isolated from Modbus specifics thus simplify Modbus integration with other protocols/networks.
|
||||
|
||||
The Data Dictionary is the list in the Modbus master which shall be defined by user to link each CID to its corresponding Modbus registers representation using Register Mapping table of the Modbus slave being used.
|
||||
Each element in this data dictionary is of type :cpp:type:`mb_parameter_descriptor_t` and represents the description of one physical characteristic:
|
||||
|
||||
.. list-table:: Table 1 Modbus master Data Dictionary description
|
||||
:widths: 8 10 82
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Description
|
||||
- Detailed information
|
||||
* - ``cid``
|
||||
- Characteristic ID
|
||||
- The identifier of characteristic (must be unique).
|
||||
* - ``param_key``
|
||||
- Characteristic Name
|
||||
- String description of the characteristic.
|
||||
* - ``param_units``
|
||||
- Characteristic Units
|
||||
- Physical Units of the characteristic.
|
||||
* - ``mb_slave_addr``
|
||||
- Modbus Slave Address
|
||||
- The short address of the device with correspond parameter UID.
|
||||
* - ``mb_param_type``
|
||||
- Modbus Register Type
|
||||
- Type of Modbus register area.
|
||||
:cpp:enumerator:`MB_PARAM_INPUT`, :cpp:enumerator:`MB_PARAM_HOLDING`, :cpp:enumerator:`MB_PARAM_COIL`, :cpp:enumerator:`MB_PARAM_DISCRETE` - represents Input , Holding, Coil and Discrete input register area accordingly;
|
||||
* - ``mb_reg_start``
|
||||
- Modbus Register Start
|
||||
- Relative register address of the characteristic in the register area.
|
||||
* - ``mb_size``
|
||||
- Modbus Register Size
|
||||
- Length of characteristic in registers (two bytes).
|
||||
* - ``param_offset``
|
||||
- Instance Offset
|
||||
- Offset to instance of the characteristic in bytes. It is used to calculate the absolute address to the characteristic in the storage structure.
|
||||
It is optional field and can be set to zero if the parameter is not used in the application.
|
||||
* - ``param_type``
|
||||
- Data Type
|
||||
- Specifies type of the characteristic. Possible types are described in the section :ref:`modbus_mapping_complex_data_types`.
|
||||
* - ``param_size``
|
||||
- Data Size
|
||||
- The storage size of the characteristic (in bytes) describes the size of data to keep into data instance during mapping. For the :ref:`modbus_mapping_complex_data_types` this allows to define the data container of the corresponded type.
|
||||
* - ``param_opts``
|
||||
- Parameter Options
|
||||
- Limits, options of characteristic used during processing of alarm in user application (optional)
|
||||
* - ``access``
|
||||
- Parameter access type
|
||||
- Can be used in user application to define the behavior of the characteristic during processing of data in user application;
|
||||
:cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`, :cpp:enumerator:`PAR_PERMS_READ`, :cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`;
|
||||
|
||||
.. note:: The ``cid`` and ``param_key`` have to be unique. Please use the prefix to the parameter key if you have several similar parameters in your register map table.
|
||||
|
||||
Examples Of Mapping
|
||||
@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
Please refer to section :ref:`modbus_mapping_complex_data_types` for more information about used data types.
|
||||
|
||||
Example 1: Configure access to legacy parameter types is described below.
|
||||
|
||||
.. list-table:: Table 2 Example Register mapping table of Modbus slave
|
||||
:widths: 5 5 2 10 5 5 68
|
||||
:header-rows: 1
|
||||
|
||||
* - CID
|
||||
- Register
|
||||
- Length
|
||||
- Range
|
||||
- Type
|
||||
- Units
|
||||
- Description
|
||||
* - 0
|
||||
- 30000
|
||||
- 4
|
||||
- MAX_UINT
|
||||
- U32
|
||||
- Not defined
|
||||
- Serial number of device (4 bytes) read-only
|
||||
* - 1
|
||||
- 30002
|
||||
- 2
|
||||
- MAX_UINT
|
||||
- U16
|
||||
- Not defined
|
||||
- Software version (4 bytes) read-only
|
||||
* - 2
|
||||
- 40000
|
||||
- 4
|
||||
- -20..40
|
||||
- FLOAT
|
||||
- DegC
|
||||
- Room temperature in DegC. Writing a temperature value to this register for single point calibration.
|
||||
* - 3
|
||||
- 40002
|
||||
- 16
|
||||
- 1..100 bytes
|
||||
- ASCII or binary array
|
||||
- Not defined
|
||||
- Device name (16 bytes) ASCII string. The type of `PARAM_TYPE_ASCII` allows to read/write complex parameter (string or binary data) that corresponds to one CID.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// Enumeration of modbus slave addresses accessed by master device
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1,
|
||||
MB_DEVICE_ADDR2,
|
||||
MB_SLAVE_COUNT
|
||||
};
|
||||
|
||||
// Enumeration of all supported CIDs for device
|
||||
enum {
|
||||
CID_SER_NUM1 = 0,
|
||||
CID_SW_VER1,
|
||||
CID_DEV_NAME1,
|
||||
CID_TEMP_DATA_1,
|
||||
CID_SER_NUM2,
|
||||
CID_SW_VER2,
|
||||
CID_DEV_NAME2,
|
||||
CID_TEMP_DATA_2
|
||||
};
|
||||
|
||||
// Example Data Dictionary for Modbus parameters in 2 slaves in the segment
|
||||
mb_parameter_descriptor_t device_parameters[] = {
|
||||
// CID, Name, Units, Modbus addr, register type, Modbus Reg Start Addr, Modbus Reg read length,
|
||||
// Instance offset (NA), Instance type, Instance length (bytes), Options (NA), Permissions
|
||||
{ CID_SER_NUM1, STR("Serial_number_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
|
||||
0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_SW_VER1, STR("Software_version_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_DEV_NAME1, STR("Device name"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 8,
|
||||
0, PARAM_TYPE_ASCII, 16, OPTS( 0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_TEMP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
|
||||
0, PARAM_TYPE_FLOAT, 4, OPTS( 16, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_SER_NUM2, STR("Serial_number_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 0, 2,
|
||||
0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_SW_VER2, STR("Software_version_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_DEV_NAME2, STR("Device name"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 8,
|
||||
0, PARAM_TYPE_ASCII, 16, OPTS( 0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_TEMP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 0, 2,
|
||||
0, PARAM_TYPE_FLOAT, 4, OPTS( 20, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
};
|
||||
// Calculate number of parameters in the table
|
||||
uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
|
||||
|
||||
Example 2: Configure access using extended parameter types for third-party devices.
|
||||
|
||||
.. list-table:: Table 3 Example Register mapping table of Modbus slave
|
||||
:widths: 2 4 2 10 3 68
|
||||
:header-rows: 1
|
||||
|
||||
* - CID
|
||||
- Register
|
||||
- Length
|
||||
- Range
|
||||
- Units
|
||||
- Description
|
||||
* - 0
|
||||
- 40000
|
||||
- 4
|
||||
- 0 ... 255
|
||||
- No units
|
||||
- :cpp:enumerator:`PARAM_TYPE_U8_A` - unsigned integer 8-bit
|
||||
* - 1
|
||||
- 40002
|
||||
- 4
|
||||
- 0 ... 65535
|
||||
- No Units
|
||||
- :cpp:enumerator:`PARAM_TYPE_U16_AB` uinsigned integer 16-bit
|
||||
* - 3
|
||||
- 40004
|
||||
- 8
|
||||
- 0 ... Unsigned integer 32-bit range
|
||||
- No units
|
||||
- :cpp:enumerator:`PARAM_TYPE_U32_ABCD` - unsigned integer 32-bit in ABCD format
|
||||
* - 4
|
||||
- 40008
|
||||
- 8
|
||||
- 0 ... Unsigned integer 32-bit range
|
||||
- No units
|
||||
- :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB` - FLOAT 32-bit value in CDAB format
|
||||
* - 5
|
||||
- 400012
|
||||
- 16
|
||||
- 0 ... Unsigned integer 64-bit range
|
||||
- No units
|
||||
- :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH` - Unsigned integer 64-bit value in ABCDEFGH format
|
||||
* - 6
|
||||
- 400020
|
||||
- 16
|
||||
- 0 ... Unsigned integer 64-bit range
|
||||
- No units
|
||||
- :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA` - Double precision 64-bit value in HGFEDCBA format
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "limits.h"
|
||||
#include "mbcontroller.h"
|
||||
|
||||
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
|
||||
#define HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
|
||||
#define HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// Example structure that contains parameter arrays of different types
|
||||
// with different options of endianness.
|
||||
typedef struct
|
||||
{
|
||||
uint16_t holding_u8_a[2];
|
||||
uint16_t holding_u16_ab[2];
|
||||
uint32_t holding_uint32_abcd[2];
|
||||
float holding_float_cdab[2];
|
||||
double holding_uint64_abcdefgh[2];
|
||||
double holding_double_hgfedcba[2];
|
||||
} holding_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
// Enumeration of modbus slave addresses accessed by master device
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1, // Short address of Modbus slave device
|
||||
MB_SLAVE_COUNT
|
||||
};
|
||||
|
||||
// Enumeration of all supported CIDs for device (used in parameter definition table)
|
||||
enum {
|
||||
CID_HOLD_U8_A = 0,
|
||||
CID_HOLD_U16_AB,
|
||||
CID_HOLD_UINT32_ABCD,
|
||||
CID_HOLD_FLOAT_CDAB,
|
||||
CID_HOLD_UINT64_ABCDEFGH,
|
||||
CID_HOLD_DOUBLE_HGFEDCBA,
|
||||
CID_COUNT
|
||||
};
|
||||
|
||||
// Example Data Dictionary for to address parameters from slaves with different options of endianness
|
||||
mb_parameter_descriptor_t device_parameters[] = {
|
||||
// CID, Name, Units, Modbus addr, register type, Modbus Reg Start Addr, Modbus Reg read length,
|
||||
// Instance offset (NA), Instance type, Instance length (bytes), Options (NA), Permissions
|
||||
{ CID_HOLD_U8_A, STR("U8_A"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_u8_a), HOLD_REG_SIZE(holding_u8_a),
|
||||
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (HOLD_REG_SIZE(holding_u8_a) << 1),
|
||||
OPTS( 0, UCHAR_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_U16_AB, STR("U16_AB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_u16_ab), HOLD_REG_SIZE(holding_u16_ab),
|
||||
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (HOLD_REG_SIZE(holding_u16_ab) << 1),
|
||||
OPTS( 0, USHRT_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_uint32_abcd), HOLD_REG_SIZE(holding_uint32_abcd),
|
||||
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (HOLD_REG_SIZE(holding_uint32_abcd) << 1),
|
||||
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_float_cdab), HOLD_REG_SIZE(holding_float_cdab),
|
||||
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (HOLD_REG_SIZE(holding_float_cdab) << 1),
|
||||
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_UINT64_ABCDEFGH, STR("UINT64_ABCDEFGH"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_uint64_abcdefgh), HOLD_REG_SIZE(holding_uint64_abcdefgh),
|
||||
HOLD_OFFSET(holding_uint64_abcdefgh), PARAM_TYPE_UINT64_ABCDEFGH, (HOLD_REG_SIZE(holding_uint64_abcdefgh) << 1),
|
||||
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
|
||||
HOLD_REG_START(holding_double_hgfedcba), HOLD_REG_SIZE(holding_double_hgfedcba),
|
||||
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
|
||||
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
|
||||
};
|
||||
uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
|
||||
|
||||
The example above describes the definition of just several extended types. The types described in the :ref:`modbus_mapping_complex_data_types` allow to address the most useful value formats from devices of known third-party vendors.
|
||||
Once the type of characteristic is defined in data dictionary the stack is responsible for conversion of values to/from the corresponding type option into the format recognizable by compiler.
|
||||
|
||||
.. note:: Please refer to your vendor device manual and its mapping table to select the types suitable for your device.
|
||||
|
||||
The Modbus stack contains also the :ref:`modbus_api_endianness_conversion` - endianness conversion API functions that allow to convert values from/to each extended type into compiler representation.
|
||||
|
||||
During initialization of the Modbus stack, a pointer to the Data Dictionary (called descriptor) must be provided as the parameter of the function below.
|
||||
|
||||
:cpp:func:`mbc_master_set_descriptor`:
|
||||
|
||||
Initialization of master descriptor. The descriptor represents an array of type :cpp:type:`mb_parameter_descriptor_t` and describes all the characteristics accessed by master.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *master_handle = NULL; // Must exist in the module and be initialized prior to call
|
||||
....
|
||||
// Set master data dictionary for initialized master instance - master_handle
|
||||
ESP_ERROR_CHECK(mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters));
|
||||
|
||||
The Data Dictionary can be initialized from SD card, MQTT or other source before start of stack. Once the initialization and setup is done, the Modbus controller allows the reading of complex parameters from any slave included in descriptor table using its CID.
|
||||
Refer to :ref:`example TCP master <example_mb_tcp_master>`, :ref:`example Serial master <example_mb_master>` for more information.
|
||||
|
||||
.. _modbus_api_master_start_communication:
|
||||
|
||||
Master Communication
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The starting of the Modbus controller is the final step in enabling communication. This is performed using function below:
|
||||
|
||||
:cpp:func:`mbc_master_start`
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *master_handle = NULL; // Pointer to allocated interface structure
|
||||
....
|
||||
esp_err_t err = mbc_master_start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb controller start fail, err = 0x%x.", (int)err);
|
||||
}
|
||||
|
||||
The list of functions below are used by the Modbus master stack from a user's application:
|
||||
|
||||
:cpp:func:`mbc_master_send_request`:
|
||||
|
||||
This function executes a blocking Modbus request. The master sends a data request (as defined in parameter request structure :cpp:type:`mb_param_request_t`) and then blocks until a response from corresponding slave and returns the status of command execution. This function provides a standard way for read/write access to Modbus devices in the network.
|
||||
|
||||
.. note:: The function can be used to form the custom request with non-standard commands to resolve compatibility issues with the custom slaves. If it is not the case the regular API should be used: :cpp:func:`mbc_master_set_parameter`, :cpp:func:`mbc_master_get_parameter`.
|
||||
|
||||
:cpp:func:`mbc_master_get_cid_info`:
|
||||
|
||||
The function gets information about each characteristic supported in the data dictionary and returns the characteristic's description in the form of the :cpp:type:`mb_parameter_descriptor_t` structure. Each characteristic is accessed using its CID.
|
||||
|
||||
:cpp:func:`mbc_master_get_parameter`
|
||||
|
||||
The function reads the data of a characteristic defined in the parameters of a Modbus slave device. The additional data for request is taken from parameter description table.
|
||||
|
||||
:cpp:func:`mbc_master_get_parameter_with`
|
||||
|
||||
The function allows to read the data of a characteristic from any slave device addressed by `uid` parameter of the function instead of slave address defined in the data dictionary. In this case the ``mb_slave_addr`` field of the parameter descriptor :cpp:type:`mb_parameter_descriptor_t` shall be equal to ``MB_SLAVE_ADDR_PLACEHOLDER``. In case of TCP type of communication the connection phase should be completed prior call of this function.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *master_handle = NULL;
|
||||
....
|
||||
const mb_parameter_descriptor_t* param_descriptor = NULL;
|
||||
uint8_t temp_data[4] = {0}; // temporary buffer to hold maximum CID size
|
||||
uint8_t type = 0;
|
||||
....
|
||||
// Get the information for characteristic cid from data dictionary
|
||||
esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
|
||||
err = mbc_master_get_parameter(master_handle, param_descriptor->cid, (uint8_t*)temp_data, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
param_descriptor->param_units,
|
||||
*(uint32_t*)temp_data);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Could not get information for characteristic %d.", cid);
|
||||
}
|
||||
|
||||
:cpp:func:`mbc_master_set_parameter`
|
||||
|
||||
The function writes characteristic's value defined as `cid` parameter in corresponded slave device. The additional data for parameter request is taken from master parameter description table.
|
||||
|
||||
:cpp:func:`mbc_master_set_parameter_with`
|
||||
|
||||
The function is similar to previous function but allows to set the data of a characteristic in any slave device addressed by `uid` parameter of the function instead of the slave address ``mb_slave_addr`` field defined in the data dictionary. The corresponded ``mb_slave_addr`` field for the characteristic in the object disctionary shall be defined as ``MB_SLAVE_ADDR_PLACEHOLDER``.
|
||||
|
||||
.. note:: When the TCP mode of communication is used the functions above additionally check the connection state of the slave being accessed and return error if the slave connection is not actual.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *master_handle = NULL;
|
||||
....
|
||||
uint8_t type = 0; // Type of parameter
|
||||
uint8_t temp_data[4] = {0}; // temporary buffer
|
||||
// Read the characteristic from slave and save the data to temp_data instance
|
||||
esp_err_t err = mbc_master_set_parameter(master_handle, CID_TEMP_DATA_2, (uint8_t*)temp_data, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Set parameter data successfully.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Set data fail, err = 0x%x (%s).", (int)err, (char*)esp_err_to_name(err));
|
||||
}
|
||||
|
||||
.. _modbus_api_master_destroy:
|
||||
|
||||
Modbus Master Teardown
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This function stops Modbus communication stack and destroys controller interface and free all used active objects.
|
||||
|
||||
:cpp:func:`mbc_master_destroy`
|
||||
|
||||
.. code:: c
|
||||
|
||||
// Pointer to allocated interface structure, must be intitialized by constructor
|
||||
static void *master_handle = NULL;
|
||||
...
|
||||
ESP_ERROR_CHECK(mbc_master_destroy(master_handle));
|
283
docs/en/overview_messaging_and_mapping.rst
Normal file
283
docs/en/overview_messaging_and_mapping.rst
Normal file
@@ -0,0 +1,283 @@
|
||||
ESP-Modbus
|
||||
==========
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Modbus serial communication protocol is de facto standard protocol widely used to connect industrial electronic devices. Modbus allows communication among many devices connected to the same network, for example, a system that measures temperature and humidity and communicates the results to a computer. The Modbus protocol uses several types of data: Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs. Versions of the Modbus protocol exist for serial port and for Ethernet and other protocols that support the Internet protocol suite. There are many variants of Modbus protocols, some of them are:
|
||||
|
||||
* ``Modbus RTU`` — This is used in serial communication and makes use of a compact, binary representation of the data for protocol communication. The RTU format follows the commands/data with a cyclic redundancy check checksum as an error check mechanism to ensure the reliability of data. Modbus RTU is the most common implementation available for Modbus. A Modbus RTU message must be transmitted continuously without inter-character hesitations. Modbus messages are framed (separated) by idle (silent) periods. The RS-485 interface communication is usually used for this type.
|
||||
* ``Modbus ASCII`` — This is used in serial communication and makes use of ASCII characters for protocol communication. The ASCII format uses a longitudinal redundancy check checksum. Modbus ASCII messages are framed by leading colon (":") and trailing newline (CR/LF).
|
||||
* ``Modbus TCP/IP or Modbus TCP`` — This is a Modbus variant used for communications over TCP/IP networks, connecting over port 502. It does not require a checksum calculation, as lower layers already provide checksum protection.
|
||||
|
||||
.. note:: This documentation (and included code snippets) requires some familiarity with the Modbus protocol. Refer to the Modbus Organization's with protocol specifications for specifics :ref:`modbus_organization`.
|
||||
|
||||
.. _modbus_supported_communication_options:
|
||||
|
||||
Modbus Supported Communication Options
|
||||
--------------------------------------
|
||||
|
||||
The Modbus library supports the standard communication options as per Modbus specification stated below.
|
||||
|
||||
.. list-table:: Standard Modbus communication options
|
||||
:widths: 10 90
|
||||
:header-rows: 1
|
||||
|
||||
* - Modbus option
|
||||
- Description of the option
|
||||
* - RTU communication
|
||||
- * 1 start bit
|
||||
* 8 data bits, least significant bit sent first
|
||||
* 1 bit for even / odd parity-no bit for no parity
|
||||
* 1 stop bit if parity is used, 2 stop bits if no parity
|
||||
* Cyclical Redundancy Check (CRC)
|
||||
* - ASCII communication
|
||||
- * 1 start bit
|
||||
* 7-8 data bits, least significant bit sent first
|
||||
* 1 bit for even / odd parity-no bit for no parity
|
||||
* 1 stop bit if parity is used, 2 stop bits if no parity
|
||||
* Longitudinal Redundancy Check (LRC)
|
||||
* - TCP communication
|
||||
- * Communications between client (master) - server (slave) over TCP/IP networks
|
||||
* Connection uses the standard port 502
|
||||
* The frames do not require checksum calculation (provided by lower layers)
|
||||
|
||||
Some vendors may use subset of communication options. In this case the detailed information is clarified in the device manual and it is possible to override the standard communication options for support of such devices.
|
||||
Please refer to :ref:`modbus_api_slave_setup_communication_options`, :ref:`modbus_api_master_setup_communication_options` for more information.
|
||||
|
||||
Messaging Model And Data Mapping
|
||||
--------------------------------
|
||||
|
||||
Modbus is an application protocol that defines rules for messaging structure and data organization that are independent of the data transmission medium. Traditional serial Modbus is a register-based protocol that defines message transactions that occur between master(s) and slave devices (multiple masters are allowed on using Modbus TCP/IP). The slave devices listen for communication from the master and simply respond as instructed. The master(s) always controls communication and may communicate directly to one slave, or all connected slaves, but the slaves cannot communicate directly with each other.
|
||||
|
||||
.. figure:: ../_static/modbus-segment.png
|
||||
:align: center
|
||||
:scale: 80%
|
||||
:alt: Modbus segment diagram
|
||||
:figclass: align-center
|
||||
|
||||
Modbus segment diagram
|
||||
|
||||
.. note:: It is assumed that the number of slaves and their register maps are known by the Modbus master before the start of stack.
|
||||
|
||||
The register map of each slave device is usually part of its device manual. A Slave device usually permits configuration of its short slave address and communication options that are used within the device's network segment.
|
||||
|
||||
The Modbus protocol allows devices to map data to four types of registers (Holding, Input, Discrete, Coil). The figure below illustrates an example mapping of a device's data to the four types of registers.
|
||||
|
||||
.. figure:: ../_static/modbus-data-mapping.png
|
||||
:align: center
|
||||
:scale: 80%
|
||||
:alt: Modbus data mapping
|
||||
:figclass: align-center
|
||||
|
||||
Modbus data mapping
|
||||
|
||||
.. _modbus_mapping_complex_data_types:
|
||||
|
||||
Mapping Of Complex Data Types
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
As per section 4.2 of Modbus specification, "MODBUS uses a ``big-Endian`` representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first". The biggest official structure defined by the Modbus specification is a 16-bit word register, which is 2 bytes. However, vendors sometimes group two or even four 16-bit registers together to be interpretted as 32-bit or 64-bit values, respectively. It is also possible when the Modbus vendors group many registers together for serial numbers, text strings, time/date, etc. Regardless of how the vendor intends the data to be interpreted, the Modbus protocol itself simply transfers 16-bit word registers. These values grouped from registers may use either little-endian or big-endian register order.
|
||||
|
||||
.. note:: Each individual 16-bit register, is encoded in big-endian order (assuming the Modbus device abides by the Modbus specification). However, the 32-bit and 64-bit types naming conventions like ABCD or ABCDEFGH, does not take into account the network format byte order of frame. For example: the ABCD prefix for 32-bit values means the common Modbus mapping format and corresponds to the CDAB on network format (order in the frame).
|
||||
|
||||
Common Data Types Supported By Modbus Vendors
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
.. list-table:: Table 1 basic types used by Modbus vendors
|
||||
:widths: 8 3 20
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Range
|
||||
- Format description
|
||||
* - U8, I8 - Unsigned/Signed 8-bit type
|
||||
- (0 .. 255)/(-128 .. 127)
|
||||
- Common unsigned 8-bit type that is stored usually in one Modbus register. The value can be stored in HI or LO byte of the register or packed with the next byte into one 16 - bit register.
|
||||
* - U16 - Unsigned integer 16-bit type
|
||||
- 0 - 65535
|
||||
- Stored in one 16-bit register. The values can be stored with AB or BA endianness.
|
||||
* - I16 - Signed integer 16-bit type
|
||||
- -32768 to 32767 is allowed.
|
||||
- Stored in one 16-bit register. The values can be stored with AB or BA forendiannessmat.
|
||||
* - I32 - Signed long integer 32-bit type
|
||||
- -2147483648 to 2147483647 is allowed.
|
||||
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness (see below).
|
||||
* - U32 - Unsigned long integer 32-bit type
|
||||
- 0 to 4294967295 is allowed.
|
||||
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness.
|
||||
* - U64 Unsigned Long long integers (Unsigned integer 64)
|
||||
- 0 to 18446744073709551615 is allowed.
|
||||
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
|
||||
* - I64 Signed Long long integers (Signed integer 64)
|
||||
- -9223372036854775808 to 9223372036854775807 is allowed.
|
||||
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
|
||||
* - Floating point single precision 32-bit
|
||||
- 1.17549435E-38 to 3.40282347E+38 is allowed.
|
||||
- Stored in two consecutive 16-bit register per IEEE754. The values can be stored with ABCD - DCBA endianness.
|
||||
* - Floating point double precision 64-bit
|
||||
- +/-5.0E-324 to +/-1.7E+308 is allowed.
|
||||
- Stored in four consecutive 16-bit register per IEEE754. The values can be stored with ABCDEFGH - BADCFEHG endianness.
|
||||
|
||||
As showed in the table above the float and double types do not fit to the 16-bit register and reguire several consecutive registers be used to store the value. However, different manufacturers store the consecutive bytes in different order (not standardized). For example: The DCBA prefix means inversed Modbus format (BADC order on network format).
|
||||
|
||||
.. list-table:: Table 2 Modbus byte order for extended types
|
||||
:widths: 3 28
|
||||
:header-rows: 1
|
||||
|
||||
* - Postfix
|
||||
- Format description
|
||||
* - ABCD
|
||||
- Big endian, high order byte first
|
||||
* - CDAB
|
||||
- Big endian, reversed register order (Little endian with byte swap)
|
||||
* - BADC
|
||||
- Little endian, reversed register order (Big endian with byte swap)
|
||||
* - DCBA
|
||||
- Little endian (Low order byte first)
|
||||
|
||||
The extended data types are used to define all possible combinations of groupped values are represented below and correspond to ``param_type`` field of the data dictionary as described in the table below:
|
||||
|
||||
.. list-table:: Table 3 Modbus extended data types of characteristics
|
||||
:widths: 6 28 10
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Format type description (common format)
|
||||
- Format type (network format)
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U8`
|
||||
- compatibility type corresponds to :cpp:enumerator:`PARAM_TYPE_U8_A`
|
||||
- Unsigned integer 8 bit type
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U16`
|
||||
- Unsigned integer 16 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U16_AB`
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U32`
|
||||
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_FLOAT`
|
||||
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_ASCII`
|
||||
- Default ASCII string format
|
||||
- Packed ASCII string data
|
||||
* - :cpp:enumerator:`PARAM_TYPE_BIN`
|
||||
- Binary data type
|
||||
- Default type for binary packed data
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I8_A`
|
||||
- I8 signed integer in low byte of register, high byte is zero
|
||||
- I8 signed integer LO
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I8_B`
|
||||
- I8 signed integer in high byte of register, low byte is zero
|
||||
- I8 signed integer HI
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U8_A`
|
||||
- U8 unsigned integer written to low byte of register, high byte is zero
|
||||
- U8 unsigned integer LO
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U8_B`
|
||||
- U8 unsigned integer written to hi byte of register, low byte is zero
|
||||
- U8 unsigned integer HI
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I16_AB`
|
||||
- I16 signed integer, big endian
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I16_BA`
|
||||
- I16 signed integer, little endian
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U16_AB`
|
||||
- U16 unsigned integer, big endian
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U16_BA`
|
||||
- U16 unsigned integer, little endian
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I32_ABCD`
|
||||
- I32 ABCD signed integer, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I32_CDAB`
|
||||
- I32 CDAB signed integer, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I32_BADC`
|
||||
- I32 BADC signed integer, little endian, reversed register order
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I32_DCBA`
|
||||
- I32 DCBA signed integer, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
|
||||
- U32 ABCD unsigned integer, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U32_CDAB`
|
||||
- U32 CDAB unsigned integer, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U32_BADC`
|
||||
- U32 BADC unsigned integer, little endian, reversed register order
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U32_DCBA`
|
||||
- U32 DCBA unsigned integer, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
|
||||
- Float ABCD floating point, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB`
|
||||
- Float CDAB floating point, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_BADC`
|
||||
- Float BADC floating point, little endian, reversed register order
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_DCBA`
|
||||
- Float DCBA floating point, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I64_ABCDEFGH`
|
||||
- I64, ABCDEFGH signed integer, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I64_HGFEDCBA`
|
||||
- I64, HGFEDCBA signed integer, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I64_GHEFCDAB`
|
||||
- I64, GHEFCDAB signed integer, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_I64_BADCFEHG`
|
||||
- I64, BADCFEHG signed integer, little endian, reversed register order
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH`
|
||||
- U64, ABCDEFGH unsigned integer, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U64_HGFEDCBA`
|
||||
- U64, HGFEDCBA unsigned integer, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U64_GHEFCDAB`
|
||||
- U64, GHEFCDAB unsigned integer, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_U64_BADCFEHG`
|
||||
- U64, BADCFEHG unsigned integer, little endian, reversed register order
|
||||
- Little endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_ABCDEFGH`
|
||||
- Double ABCDEFGH floating point, big endian
|
||||
- Little endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA`
|
||||
- Double HGFEDCBA floating point, little endian
|
||||
- Big endian byte swap
|
||||
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_GHEFCDAB`
|
||||
- Double GHEFCDAB floating point, big endian, reversed register order
|
||||
- Big endian
|
||||
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_BADCFEHG`
|
||||
- Double BADCFEHG floating point, little endian, reversed register order
|
||||
- Little endian
|
||||
|
||||
.. note:: The support for the extended data types should be enabled using the option ``CONFIG_FMB_EXT_TYPE_SUPPORT`` in kconfig menu.
|
||||
|
||||
The below diagrams show how the extended data types appear on network layer.
|
||||
|
||||
.. blockdiag:: /../_static/diag_frame.diag
|
||||
:scale: 80%
|
||||
:caption: Modbus master response with ABCD frame
|
||||
:align: center
|
||||
|
||||
.. blockdiag:: /../_static/modbus_frame_examples.diag
|
||||
:scale: 80%
|
||||
:caption: Modbus frame packaging examples (16-bit, 32-bit, 64-bit data)
|
||||
:align: center
|
||||
|
||||
The approach showed above can be used to pack the data into MBAP frames used by Modbus TCP as well as for other types with similar size.
|
||||
|
||||
The following sections give an overview of how to use the ESP_Modbus component found under `components/freemodbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly:
|
||||
|
||||
- :ref:`modbus_api_port_initialization`
|
||||
- :ref:`modbus_api_slave_overview`
|
||||
- :ref:`modbus_api_master_overview`
|
172
docs/en/port_initialization.rst
Normal file
172
docs/en/port_initialization.rst
Normal file
@@ -0,0 +1,172 @@
|
||||
.. _modbus_api_port_initialization:
|
||||
|
||||
Modbus Port Initialization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ESP_Modbus supports Modbus SERIAL and TCP communication objects and an object must be initialized before calling any other Modbus API. The functions below are used to create and then initialize Modbus controller interface (either master or slave) over a particular transmission medium (either Serial or TCP/IP):
|
||||
|
||||
- :cpp:func:`mbc_slave_create_serial`
|
||||
- :cpp:func:`mbc_master_create_serial`
|
||||
- :cpp:func:`mbc_master_create_tcp`
|
||||
- :cpp:func:`mbc_slave_create_tcp`
|
||||
|
||||
Calling the constructor function allows to create communication object with the specific communication options be defined in the configuration structure. The pointer to communication object is returned by constructor API and is being used as a handle for each following API call.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// Pointer to allocate interface structure
|
||||
// is used later as a first parameter for each API call
|
||||
static void *master_handle = NULL;
|
||||
ESP_ERROR_CHECK(mbc_master_create_serial(&config, &master_handle));
|
||||
...
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *master_handle = NULL;
|
||||
ESP_ERROR_CHECK(mbc_master_create_tcp(&config, &master_handle));
|
||||
...
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *slave_handle = NULL;
|
||||
ESP_ERROR_CHECK(mbc_slave_create_tcp(&config, &slave_handle));
|
||||
...
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *slave_handle = NULL;
|
||||
ESP_ERROR_CHECK(mbc_slave_create_serial(&config, &slave_handle));
|
||||
...
|
||||
|
||||
Refer to :ref:`modbus_api_master_setup_communication_options` and :ref:`modbus_api_slave_setup_communication_options` for more information on how to configure communication options for the master and slave object accordingly.
|
||||
|
||||
.. _modbus_api_master_setup_communication_options:
|
||||
|
||||
Master Communication Options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The configuration structure is used to recognize the type of object being initialized. An example of initialization for Modbus serial master in RTU is below. The configuration structure provided as a parameter and is different for serial and TCP communication mode.
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_PORT_NUM 2
|
||||
#define MB_DEV_SPEED 115200
|
||||
static void *master_handle = NULL;
|
||||
....
|
||||
// Initialize Modbus controller
|
||||
mb_communication_info_t config = {
|
||||
.ser_opts.port = MB_PORT_NUM, // master communication port number
|
||||
.ser_opts.mode = MB_RTU, // mode of Modbus communication (MB_RTU, MB_ASCII)
|
||||
.ser_opts.baudrate = MB_DEV_SPEED, // baud rate of the port
|
||||
.ser_opts.parity = MB_PARITY_NONE, // parity option for the port
|
||||
.ser_opts.uid = 0, // unused for master
|
||||
.ser_opts.response_tout_ms = 1000, // slave response time for master (if = 0, taken from default config)
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS, // number of data bits for communication port
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1 // number of stop bits for the communication port
|
||||
};
|
||||
esp_err_t err = mbc_master_create_serial(&config, &master_handle);
|
||||
if (master_handler == NULL || err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb controller initialization fail.");
|
||||
}
|
||||
|
||||
.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to the `UART communication section <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#uart-api-running-uart-communication>`__ in documentation.
|
||||
|
||||
An example of initialization for Modbus TCP master is below. The Modbus master TCP requires additional definition of IP address table where number of addresses should be equal to number of unique slave addresses in master Modbus Data Dictionary. The Unit Identifier defined in the table below corresponds to UID (slave short address field) in the Data Dictionary.
|
||||
The format of slave definition following the notation `UID;slave_host_ip_or_dns_name;port_number` and allows some variations as described in the example below.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// This is public pointer for the module and used by master
|
||||
// to resolve slave addresses and reconnect when connection is broken
|
||||
static char *slave_ip_address_table[] = {
|
||||
"01;mb_slave_tcp_01;502", // Define the slave using mdns host name ("mb_slave_tcp_01") with UID = 01 and communication port 502
|
||||
"200;mb_slave_tcp_c8;1502", // Definition of slave with mdns name "mb_slave_tcp_C8" and UID = 200, port = 1502
|
||||
"35;192.168.32.54;1502", // Definition of slave with the static IPV4 address and UID = 35, port = 502
|
||||
"12:2001:0db8:85a3:0000:0000:8a2e:0370:7334:502", // Definition of the slave with static IPV6 address and UID = 12, port = 502
|
||||
NULL // End of table condition (must be included)
|
||||
};
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_TCP_PORT 502
|
||||
static void *master_handle = NULL;
|
||||
....
|
||||
mb_communication_info_t tcp_master_config = {
|
||||
.tcp_opts.port = MB_TCP_PORT, // Default TCP Port number
|
||||
.tcp_opts.mode = MB_TCP, // TCP mode of communication
|
||||
.tcp_opts.addr_type = MB_IPV4, // type of IP address (MB_IPV4, MB_IPV6)
|
||||
.tcp_opts.ip_addr_table = (void *)slave_ip_address_table, // list of slaves for master (must be defined)
|
||||
.tcp_opts.uid = 0, // the UID unused for master
|
||||
.tcp_opts.start_disconnected = false, // false - manage connections to all slaves before start
|
||||
.tcp_opts.response_tout_ms = 2000, // slave response time in milliseconds for master, 0 - use default konfig
|
||||
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(), // the pointer to netif inteface
|
||||
};
|
||||
esp_err_t err = mbc_master_create_tcp(pcomm_info, &master_handle);
|
||||
if (master_handler == NULL || err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb controller initialization fail.");
|
||||
}
|
||||
|
||||
.. note:: Refer to `esp_netif component <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_netif.html>`__ for more information about network interface initialization.
|
||||
|
||||
The slave IP addresses of the slaves can be resolved automatically by the stack using mDNS service as described in the example. In this case each slave has to use the mDNS service support and define its host name appropriately.
|
||||
Refer to :ref:`example TCP master <example_mb_tcp_master>`, :ref:`example TCP slave <example_mb_tcp_slave>` for more information.
|
||||
|
||||
.. note:: The Modbus Master TCP functionality is under testing and competition status will be announced later over official channels.
|
||||
|
||||
.. _modbus_api_slave_setup_communication_options:
|
||||
|
||||
Slave Communication Options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The function initializes the Modbus controller interface and its active context (tasks, RTOS objects and other resources).
|
||||
|
||||
This example code to initialize Modbus serial slave:
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_PORT_NUM 2
|
||||
#define MB_DEV_SPEED 115200
|
||||
#define MB_SLAVE_ADDR 1
|
||||
static void* slave_handle = NULL;
|
||||
....
|
||||
mb_communication_info_t config = {
|
||||
.ser_opts.port = MB_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII, // ASCII communication mode
|
||||
.ser_opts.baudrate = MB_DEV_SPEED,
|
||||
.ser_opts.parity = MB_PARITY_NONE,
|
||||
.ser_opts.uid = MB_SLAVE_ADDR, // Modbus slave UID - Unit Identifier (short address)
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1
|
||||
};
|
||||
// Initialization and setup of Modbus serial slave in ASCII communication mode
|
||||
esp_err_t err = mbc_slave_create_serial(&config, &slave_handle);
|
||||
if (slave_handle == NULL || err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb controller initialization fail.");
|
||||
}
|
||||
|
||||
.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to the `UART communication section <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#uart-api-running-uart-communication>`__ in documentation.
|
||||
|
||||
This example code to initialize Modbus TCP slave:
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_SLAVE_ADDR 1
|
||||
#define MB_TCP_PORT_NUMBER 1502
|
||||
static void* slave_handle = NULL;
|
||||
....
|
||||
mb_communication_info_t tcp_slave_config = {
|
||||
.tcp_opts.port = MB_TCP_PORT_NUMBER, // communication port number for Modbus slave
|
||||
.tcp_opts.mode = MB_TCP, // mode of communication for slave
|
||||
.tcp_opts.addr_type = MB_IPV4, // type of addressing being used
|
||||
.tcp_opts.ip_addr_table = NULL, // Bind to any address
|
||||
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(),// the pointer to netif inteface
|
||||
.tcp_opts.uid = MB_SLAVE_ADDR // Modbus slave Unit Identifier
|
||||
};
|
||||
esp_err_t err = mbc_slave_create_tcp(&tcp_slave_config, &slave_handle);
|
||||
if (slave_handle == NULL || err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb controller initialization fail.");
|
||||
}
|
||||
|
||||
.. note:: Refer to `esp_netif component <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_netif.html>`__ for more information about network interface initialization.
|
||||
|
||||
.. note:: The Modbus Slave TCP functionality is under testing and the competition status will be announced later over official channels.
|
213
docs/en/slave_api_overview.rst
Normal file
213
docs/en/slave_api_overview.rst
Normal file
@@ -0,0 +1,213 @@
|
||||
.. _modbus_api_slave_overview:
|
||||
|
||||
Modbus Slave API Overview
|
||||
-------------------------
|
||||
|
||||
The sections below represent typical programming workflow for the slave API which should be called in following order:
|
||||
|
||||
1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface using communication options.
|
||||
2. :ref:`modbus_api_slave_configure_descriptor` - Configure data descriptors to access slave parameters.
|
||||
3. :ref:`modbus_api_slave_setup_communication_options` - Allows to setup communication options for selected port.
|
||||
4. :ref:`modbus_api_slave_communication` - Start stack and sending / receiving data. Filter events when master accesses the register areas.
|
||||
5. :ref:`modbus_api_slave_destroy` - Destroy Modbus controller and its resources.
|
||||
|
||||
.. _modbus_api_slave_configure_descriptor:
|
||||
|
||||
Configuring Slave Data Access
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following functions must be called when the Modbus controller slave port is already initialized. Refer to :ref:`modbus_api_port_initialization`.
|
||||
|
||||
The slave stack requires the user to define structures (memory storage areas) that store the Modbus parameters accessed by stack. These structures should be prepared by the user and be assigned to the Modbus controller interface using :cpp:func:`mbc_slave_set_descriptor` API call before the start of communication. The slave task can call the :cpp:func:`mbc_slave_check_event` function which will block until the Modbus master access the slave. The slave task can then get information about the data being accessed.
|
||||
|
||||
.. note:: One slave can define several area descriptors per each type of Modbus register area with different start_offset.
|
||||
|
||||
Register area is defined by using the :cpp:type:`mb_register_area_descriptor_t` structure.
|
||||
|
||||
.. list-table:: Table 3 Modbus register area descriptor
|
||||
:widths: 8 92
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Description
|
||||
* - ``start_offset``
|
||||
- Zero based register relative offset for defined register area. Example: register address = 40002 ( 4x register area - Function 3 - holding register ), start_offset = 2
|
||||
* - ``type``
|
||||
- Type of the Modbus register area. Refer to :cpp:type:`mb_param_type_t` for more information.
|
||||
* - ``address``
|
||||
- A pointer to the memory area which is used to store the register data for this area descriptor.
|
||||
* - ``size``
|
||||
- The size of the memory area in bytes which is used to store register data.
|
||||
|
||||
:cpp:func:`mbc_slave_set_descriptor`
|
||||
|
||||
The function initializes Modbus communication descriptors for each type of Modbus register area (Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs). Once areas are initialized and the :cpp:func:`mbc_slave_start()` API is called the Modbus stack can access the data in user data structures by request from master.
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_REG_INPUT_START_AREA0 (0)
|
||||
#define MB_REG_HOLDING_START_AREA0 (0)
|
||||
#define MB_REG_HOLD_CNT (100)
|
||||
#define MB_REG_INPUT_CNT (100)
|
||||
....
|
||||
static void *slave_handle = NULL; // Pointer to interface structure allocated by constructor
|
||||
....
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
unit16_t holding_reg_area[MB_REG_HOLD_CNT] = {0}; // storage area for holding registers
|
||||
unit16_t input_reg_area[MB_REG_INPUT_CNT] = {0}; // storage area for input registers
|
||||
|
||||
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_area[0]; // Set pointer to storage instance
|
||||
reg_area.size = (sizeof(holding_reg_area) << 1); // Set the size of register storage area in bytes!
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(slave_handle, reg_area));
|
||||
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
|
||||
reg_area.address = (void*)&input_reg_area[0];
|
||||
reg_area.size = (sizeof(input_reg_area) << 1);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(slave_handle, reg_area));
|
||||
|
||||
|
||||
At least one area descriptor per each Modbus register type must be set in order to provide register access to its area. If the master tries to access an undefined area, the stack will generate a Modbus exception.
|
||||
|
||||
The stack supports the extended data types when enabled through the the option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` in kconfig menu.
|
||||
In this case the mapped data values can be initialized to specific format using :ref:`modbus_api_endianness_conversion`.
|
||||
Please refer to secton :ref:`modbus_mapping_complex_data_types` for more information about data types.
|
||||
|
||||
Example initialization of mapped values:
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
val_32_arr holding_float_abcd[2] = {0};
|
||||
val_64_arr holding_double_ghefcdab[2] = {0};
|
||||
...
|
||||
// set the Modbus parameter to specific format
|
||||
portENTER_CRITICAL(¶m_lock); // critical section is required if the stack is active
|
||||
mb_set_float_abcd(&holding_float_abcd[0], (float)12345.0);
|
||||
mb_set_float_abcd(&holding_float_abcd[1], (float)12345.0);
|
||||
mb_set_double_ghefcdab(&holding_double_ghefcdab[0], (double)12345.0);
|
||||
portEXIT_CRITICAL(¶m_lock);
|
||||
...
|
||||
// The actual abcd formatted value can be converted to actual float represenatation as below
|
||||
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[0]));
|
||||
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[1]));
|
||||
ESP_LOGI("TEST", "Test value ghefcdab: %lf", mb_get_double_ghefcdab(&holding_double_ghefcdab[0]));
|
||||
...
|
||||
|
||||
.. _modbus_api_slave_communication:
|
||||
|
||||
Slave Communication
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The function below is used to start Modbus controller interface and allows communication.
|
||||
|
||||
:cpp:func:`mbc_slave_start`
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void* slave_handle = NULL;
|
||||
....
|
||||
ESP_ERROR_CHECK(mbc_slave_start(slave_handle)); // The handle must be initialized prior to start call.
|
||||
|
||||
:cpp:func:`mbc_slave_check_event`
|
||||
|
||||
The blocking call to function waits for a event specified (represented as an event mask parameter). Once the master accesses the parameter and the event mask matches the parameter type, the application task will be unblocked and function will return the corresponding event :cpp:type:`mb_event_group_t` which describes the type of register access being done.
|
||||
|
||||
:cpp:func:`mbc_slave_get_param_info`
|
||||
|
||||
The function gets information about accessed parameters from the Modbus controller event queue. The KConfig ``CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE`` key can be used to configure the notification queue size. The timeout parameter allows a timeout to be specified when waiting for a notification. The :cpp:type:`mb_param_info_t` structure contains information about accessed parameter.
|
||||
|
||||
.. list-table:: Table 4 Description of the register info structure: :cpp:type:`mb_param_info_t`
|
||||
:widths: 10 90
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Description
|
||||
* - ``time_stamp``
|
||||
- the time stamp of the event when defined parameter is accessed
|
||||
* - ``mb_offset``
|
||||
- start Modbus register accessed by master
|
||||
* - ``type``
|
||||
- type of the Modbus register area being accessed (See the :cpp:type:`mb_event_group_t` for more information)
|
||||
* - ``address``
|
||||
- memory address that corresponds to accessed register in defined area descriptor
|
||||
* - ``size``
|
||||
- number of registers being accessed by master
|
||||
|
||||
Example to get event when holding or input registers accessed in the slave:
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD | MB_EVENT_HOLDING_REG_RD)
|
||||
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR)
|
||||
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
|
||||
#define MB_PAR_INFO_GET_TOUT (10 / portTICK_RATE_MS)
|
||||
....
|
||||
static void *slave_handle = NULL; // communication object handle
|
||||
....
|
||||
// Get the mask of the queued events, the function
|
||||
// blocks while waiting for register access
|
||||
(void)mbc_slave_check_event(mbc_slave_handle, MB_READ_WRITE_MASK);
|
||||
// Obtain the parameter information from parameter queue regarding access from master
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(mbc_slave_handle, ®_info, MB_PAR_INFO_GET_TOUT));
|
||||
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
|
||||
// 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 (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (reg_info.type & (MB_EVENT_INPUT_REG_RD)) {
|
||||
ESP_LOGI(TAG, "INPUT %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
}
|
||||
|
||||
:cpp:func:`mbc_slave_lock`
|
||||
|
||||
:cpp:func:`mbc_slave_unlock`
|
||||
|
||||
The direct access to slave register area from user application must be protected by critical section. The following functions can be used to protect access to the data from registered mapping area while the communication object is active.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void *slave_handle = NULL; // communication object handle
|
||||
...
|
||||
(void)mbc_slave_lock(slave_handle); // ignore the returned error if the object is not actual
|
||||
holding_reg_area[1] += 10; // the data is part of initialized register area accessed by slave
|
||||
(void)mbc_slave_unlock(slave_handle);
|
||||
|
||||
The access to registered area shared between several slave objects from user application must be protected by critical section base on spin lock:
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
...
|
||||
static portMUX_TYPE g_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
...
|
||||
portENTER_CRITICAL(¶m_lock);
|
||||
holding_reg_area[2] = 123;
|
||||
portEXIT_CRITICAL(¶m_lock);
|
||||
|
||||
.. _modbus_api_slave_destroy:
|
||||
|
||||
Modbus Slave Teardown
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This function stops the Modbus communication stack, destroys the controller interface, and frees all used active objects allocated for the slave.
|
||||
|
||||
:cpp:func:`mbc_slave_delete`
|
||||
|
||||
.. code:: c
|
||||
|
||||
ESP_ERROR_CHECK(mbc_slave_delete(slave_handle)); // delete the master communication object defined by its handle
|
39
docs/generate_docs
Executable file
39
docs/generate_docs
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
exit_if_error() {
|
||||
local exit_code=$1
|
||||
shift
|
||||
[[ $exit_code ]] && # do nothing if no error code passed
|
||||
((exit_code != 0)) && { # do nothing if error code is 0
|
||||
printf 'ERROR: %s\n' "$@" >&2
|
||||
exit "$exit_code"
|
||||
}
|
||||
}
|
||||
|
||||
rm -rf _build
|
||||
build-docs --target esp32 --language en || exit_if_error $? "Documentation build fail."
|
||||
|
||||
# Modifes target field of html files
|
||||
ELEMENT="<script type='text/javascript'>
|
||||
window.onload =(function() {
|
||||
var myAnchor = document.getElementById('target-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.style.float = 'left';
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'all targets';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
})();
|
||||
</script>"
|
||||
|
||||
FILES=$(find . -path "*/_build/en/esp32/html/*.html")
|
||||
|
||||
for FILE in ${FILES}
|
||||
do
|
||||
echo ${ELEMENT} >> "${FILE}"
|
||||
done
|
||||
|
||||
exit_if_error $? "Documentation build fail."
|
||||
|
||||
echo "Documentation build ok."
|
2
docs/requirements.txt
Normal file
2
docs/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
esp-docs>=1.9.1,<2.0
|
||||
Pillow==9.5.0
|
18
docs/utils.sh
Normal file
18
docs/utils.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
# Bash helper functions for adding SSH keys
|
||||
|
||||
function add_ssh_keys() {
|
||||
local key_string="${1}"
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
echo -n "${key_string}" >~/.ssh/id_rsa_base64
|
||||
base64 --decode --ignore-garbage ~/.ssh/id_rsa_base64 >~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
}
|
||||
|
||||
function add_doc_server_ssh_keys() {
|
||||
local key_string="${1}"
|
||||
local server_url="${2}"
|
||||
local server_user="${3}"
|
||||
add_ssh_keys "${key_string}"
|
||||
echo -e "Host ${server_url}\n\tStrictHostKeyChecking no\n\tUser ${server_user}\n" >>~/.ssh/config
|
||||
}
|
27
examples/.build-test-rules.yml
Normal file
27
examples/.build-test-rules.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
tcp/mb_tcp_master:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
|
||||
reason: only manual test is performed
|
||||
disable:
|
||||
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1
|
||||
|
||||
tcp/mb_tcp_slave:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
|
||||
reason: only manual test is performed
|
||||
disable:
|
||||
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1
|
||||
|
||||
serial/mb_serial_master:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
|
||||
reason: only manual test is performed
|
||||
disable:
|
||||
- if: CONFIG_NAME == "default" and SOC_WIFI_SUPPORTED != 1
|
||||
|
||||
serial/mb_serial_slave:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
|
||||
reason: only manual test is performed
|
||||
|
||||
|
9
examples/README.md
Normal file
9
examples/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
|
325
examples/conftest.py
Normal file
325
examples/conftest.py
Normal file
@@ -0,0 +1,325 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# pylint: disable=W0621 # redefined-outer-name
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, Match, Optional, TextIO, Tuple
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
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
|
||||
|
||||
|
||||
class Stages(Enum):
|
||||
STACK_DEFAULT = 1
|
||||
STACK_IPV4 = 2
|
||||
STACK_IPV6 = 3
|
||||
STACK_INIT = 4
|
||||
STACK_CONNECT = 5
|
||||
STACK_START = 6
|
||||
STACK_PAR_OK = 7
|
||||
STACK_PAR_FAIL = 8
|
||||
STACK_DESTROY = 9
|
||||
|
||||
DEFAULT_SDKCONFIG = 'default'
|
||||
ALLOWED_PERCENT_OF_FAILS = 10
|
||||
|
||||
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]*)'
|
||||
|
||||
TEST_EXPECT_STR_TIMEOUT = 120
|
||||
TEST_ACK_TIMEOUT = 60
|
||||
TEST_MAX_CIDS = 8
|
||||
|
||||
app: IdfApp
|
||||
serial: IdfSerial
|
||||
|
||||
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.test_stage = Stages.STACK_DEFAULT
|
||||
self.dictionary = None
|
||||
self.test_finish = False
|
||||
self.test_status = False
|
||||
|
||||
def close(self) -> None:
|
||||
super().close()
|
||||
|
||||
def dut_get_ip(self) -> Optional[str]:
|
||||
if self.ip_address is None:
|
||||
expect_address = self.expect(self.TEST_IP_ADDRESS_REGEXP, timeout=self.TEST_EXPECT_STR_TIMEOUT)
|
||||
if isinstance(expect_address, Match):
|
||||
self.ip_address = expect_address.group(1).decode('ascii')
|
||||
return self.ip_address
|
||||
|
||||
def dut_get_name(self) -> Optional[str]:
|
||||
if self.app_name is None:
|
||||
expect_name = self.expect(self.TEST_APP_NAME, timeout=self.TEST_EXPECT_STR_TIMEOUT)
|
||||
if isinstance(expect_name, Match):
|
||||
self.app_name = expect_name.group(1).decode('ascii')
|
||||
return self.app_name
|
||||
|
||||
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
|
||||
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):
|
||||
message = r'IP{}={}'.format(addr_num, slave_ip)
|
||||
self.logger.info('{} sent to master'.format(message))
|
||||
self.write(message)
|
||||
return addr_num
|
||||
|
||||
def get_expect_proc(self) -> Optional[object]:
|
||||
expect_proc: object = None
|
||||
try:
|
||||
expect_proc = self.__getattribute__('pexpect_proc')
|
||||
except:
|
||||
expect_proc = self.__getattribute__('_p')
|
||||
finally:
|
||||
if (expect_proc and callable(getattr(expect_proc, 'expect'))):
|
||||
return expect_proc
|
||||
else :
|
||||
return None
|
||||
|
||||
def expect_any(self, *expect_items: Tuple[str, Callable], timeout: Optional[int]) -> None:
|
||||
"""
|
||||
expect_any(*expect_items, timeout=DEFAULT_TIMEOUT)
|
||||
expect any of the patterns.
|
||||
will call callback (if provided) if pattern match succeed and then return.
|
||||
will pass match result to the callback.
|
||||
|
||||
:raise ExpectTimeout: failed to match any one of the expect items before timeout
|
||||
:raise UnsupportedExpectItem: pattern in expect_item is not string or compiled RegEx
|
||||
|
||||
:arg expect_items: one or more expect items.
|
||||
string, compiled RegEx pattern or (string or RegEx(string pattern), callback)
|
||||
:keyword timeout: timeout for expect
|
||||
:return: matched item
|
||||
"""
|
||||
def process_expected_item(item_raw: Tuple[str, Callable[..., Any]]) -> Dict[str, Any]:
|
||||
# convert item raw data to standard dict
|
||||
item = {
|
||||
'pattern': item_raw[0] if isinstance(item_raw, tuple) else item_raw,
|
||||
'callback': item_raw[1] if isinstance(item_raw, tuple) else None,
|
||||
'index': -1,
|
||||
'ret': None,
|
||||
}
|
||||
return item
|
||||
|
||||
expect_items_list = [process_expected_item(item) for item in expect_items]
|
||||
expect_patterns = [item['pattern'] for item in expect_items_list if item['pattern'] is not None]
|
||||
match_item = None
|
||||
|
||||
# Workaround: We need to use the original expect method of pexpect process which returns
|
||||
# index of matched pattern instead of Match object returned by dut.expect()
|
||||
expect_proc: Optional[object] = self.get_expect_proc()
|
||||
|
||||
if expect_proc is not None:
|
||||
match_index = expect_proc.expect(expect_patterns, timeout)
|
||||
|
||||
if isinstance(match_index, int):
|
||||
match_item = expect_items_list[match_index] # type: ignore
|
||||
match_item['index'] = match_index # type: ignore , keep match index
|
||||
if isinstance(expect_proc.match, Match) and len(expect_proc.match.groups()) > 0:
|
||||
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.', self.app_name)
|
||||
raise RuntimeError from None
|
||||
|
||||
def dut_test_start(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:
|
||||
""" Handle get_ip v4
|
||||
"""
|
||||
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:
|
||||
""" 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:
|
||||
""" 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:
|
||||
""" 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:
|
||||
""" 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:
|
||||
""" 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
|
||||
self.test_stage = Stages.STACK_PAR_OK
|
||||
|
||||
def handle_par_fail(data: Optional[Any]) -> None:
|
||||
""" Handle parameter fail
|
||||
"""
|
||||
self.logger.info('%s[READ_PAR_FAIL]: %s', self.app_name, str(data))
|
||||
self.param_fail_count += 1
|
||||
self.test_stage = Stages.STACK_PAR_FAIL
|
||||
|
||||
def handle_destroy(data: Optional[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.test_finish = True
|
||||
|
||||
while not self.test_finish:
|
||||
try:
|
||||
self.expect_any((dictionary[Stages.STACK_IPV4], handle_get_ip4),
|
||||
(dictionary[Stages.STACK_IPV6], handle_get_ip6),
|
||||
(dictionary[Stages.STACK_INIT], handle_init),
|
||||
(dictionary[Stages.STACK_CONNECT], handle_connect),
|
||||
(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_DESTROY], handle_destroy),
|
||||
timeout=timeout_value)
|
||||
except pexpect.TIMEOUT:
|
||||
self.logger.info('%s, expect timeout on stage %s (%s seconds)', self.app_name, self.test_stage.name, timeout_value)
|
||||
self.test_finish = True
|
||||
|
||||
def dut_check_errors(self) -> None:
|
||||
''' Verify allowed percentage of errors for the dut
|
||||
'''
|
||||
allowed_ok_percentage = ((self.param_ok_count / (self.param_ok_count + self.param_fail_count + 1)) * 100)
|
||||
if self.param_ok_count and (allowed_ok_percentage > (100 - ALLOWED_PERCENT_OF_FAILS)):
|
||||
self.logger.info('%s: ok_count: %d, fail count: %d', self.app_name, self.param_ok_count, self.param_fail_count)
|
||||
else :
|
||||
self.logger.error('%s: ok_count: %d, number of failed readings %d exceeds %d percent', self.app_name, self.param_ok_count, self.param_fail_count, ALLOWED_PERCENT_OF_FAILS)
|
||||
raise RuntimeError from None
|
||||
|
||||
############
|
||||
# Fixtures #
|
||||
############
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def session_tempdir() -> str:
|
||||
|
||||
_tmpdir = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'pytest_embedded_log',
|
||||
datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
|
||||
)
|
||||
os.makedirs(_tmpdir, exist_ok=True)
|
||||
return _tmpdir
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@multi_dut_fixture
|
||||
def junit_properties(
|
||||
test_case_name: str, record_xml_attribute: Callable[[str, object], None]
|
||||
) -> None:
|
||||
"""
|
||||
This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name>
|
||||
"""
|
||||
record_xml_attribute('name', test_case_name)
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
|
||||
mp = MonkeyPatch()
|
||||
request.addfinalizer(mp.undo)
|
||||
return mp
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
|
||||
monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', ModbusTestDut)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_argument
|
||||
def config(request: FixtureRequest) -> str:
|
||||
return getattr(request, 'param', None) or DEFAULT_SDKCONFIG
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_fixture
|
||||
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:
|
||||
"""
|
||||
Check local build dir with the following priority:
|
||||
|
||||
1. build_<target>_<config>
|
||||
2. build_<target>
|
||||
3. build_<config>
|
||||
4. build
|
||||
|
||||
Args:
|
||||
app_path: app path
|
||||
target: target
|
||||
config: config
|
||||
|
||||
Returns:
|
||||
valid build directory
|
||||
"""
|
||||
check_dirs = []
|
||||
|
||||
if target is not None and config is not None:
|
||||
check_dirs.append(f'build_{target}_{config}')
|
||||
if target is not None:
|
||||
check_dirs.append(f'build_{target}')
|
||||
if config is not None:
|
||||
check_dirs.append(f'build_{config}')
|
||||
check_dirs.append('build')
|
||||
|
||||
for check_dir in check_dirs:
|
||||
binary_path = os.path.join(app_path, check_dir)
|
||||
if os.path.isdir(binary_path):
|
||||
logging.info(f'find valid binary path: {binary_path}')
|
||||
return check_dir
|
||||
|
||||
logging.warning(
|
||||
'checking binary path: %s... missing... try another place', binary_path
|
||||
)
|
||||
|
||||
if config is not None and 'dummy' in config:
|
||||
logging.warning('no build dir valid for application: %s, config: %s. Skip test.', binary_path, config)
|
||||
return None
|
||||
|
||||
recommend_place = check_dirs[0]
|
||||
logging.error(
|
||||
f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again'
|
||||
)
|
||||
|
||||
sys.exit(1)
|
5
examples/mb_example_common/CMakeLists.txt
Normal file
5
examples/mb_example_common/CMakeLists.txt
Normal 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
examples/mb_example_common/README.md
Normal file
10
examples/mb_example_common/README.md
Normal 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
examples/mb_example_common/component.mk
Normal file
5
examples/mb_example_common/component.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := .
|
102
examples/mb_example_common/include/modbus_params.h
Normal file
102
examples/mb_example_common/include/modbus_params.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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, discreet 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
examples/mb_example_common/modbus_params.c
Normal file
21
examples/mb_example_common/modbus_params.c
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 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 };
|
82
examples/serial/README.md
Normal file
82
examples/serial/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 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.
|
||||
|
||||
|
9
examples/serial/mb_serial_master/CMakeLists.txt
Normal file
9
examples/serial/mb_serial_master/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_serial_master)
|
156
examples/serial/mb_serial_master/README.md
Normal file
156
examples/serial/mb_serial_master/README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus Master Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device.
|
||||
This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file.
|
||||
The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment.
|
||||
The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of holding_data0 parameter exceeded limit.
|
||||
The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
Example parameters definition:
|
||||
--------------------------------------------------------------------------------------------------
|
||||
| Slave Address | Characteristic ID | Characteristic name | Description |
|
||||
|---------------------|----------------------|----------------------|----------------------------|
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off |
|
||||
--------------------------------------------------------------------------------------------------
|
||||
Note: The Slave Address is the same for all parameters for example test but it can be changed in the ```Example Data (Object) Dictionary``` table of master example to address parameters from other slaves.
|
||||
The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment.
|
||||
|
||||
Simplified Modbus connection schematic for example test:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
------------- -------------
|
||||
| | RS485 network | |
|
||||
| Slave 1 |---<>--+---<>---| Master |
|
||||
| | | |
|
||||
------------- -------------
|
||||
```
|
||||
Modbus multi slave segment connection schematic:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
-------------
|
||||
| |
|
||||
| Slave 1 |---<>--+
|
||||
| | |
|
||||
------------- |
|
||||
MB_DEVICE_ADDR2 |
|
||||
------------- | -------------
|
||||
| | | | |
|
||||
| Slave 2 |---<>--+---<>---| Master |
|
||||
| | | | |
|
||||
------------- | -------------
|
||||
MB_DEVICE_ADDR3 |
|
||||
------------- RS485 network
|
||||
| | |
|
||||
| Slave 3 |---<>--+
|
||||
| |
|
||||
-------------
|
||||
```
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board
|
||||
|
||||
Option 2:
|
||||
Several ESP32 boards flashed with modbus_slave example software to represent slave device with specific slave address (See CONFIG_MB_SLAVE_ADDR). The slave addresses for each board have to be configured as defined in "connection schematic" above.
|
||||
One ESP32 board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below).
|
||||
|
||||
The MAX485 line driver is used as an example below but other similar chips can be used as well.
|
||||
RS485 example circuit schematic for connection of master and slave devices into segment:
|
||||
```
|
||||
VCC ---------------+ +--------------- VCC
|
||||
| |
|
||||
+-------x-------+ +-------x-------+
|
||||
RXD <------| RO | DIFFERENTIAL | RO|-----> RXD
|
||||
| B|---------------|B |
|
||||
TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD
|
||||
ESP32 BOARD | | RS-485 side | | External PC (emulator) with USB to serial or
|
||||
RTS --+--->| DE | / \ | DE|---+ ESP32 BOARD (slave)
|
||||
| | A|---------------|A | |
|
||||
+----| /RE | PAIR | /RE|---+-- RTS
|
||||
+-------x-------+ +-------x-------+
|
||||
| |
|
||||
--- ---
|
||||
Modbus Master device Modbus Slave device
|
||||
|
||||
```
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to setup configuration:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
Configure the UART pins used for modbus communication using and table below.
|
||||
Define the communication mode parameter for master and slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave devices in one segment).
|
||||
Configure the slave address for each slave in the Modbus segment (the CONFIG_MB_SLAVE_ADDR in Kconfig).
|
||||
```
|
||||
------------------------------------------------------------------------------------------------------------------------------
|
||||
| UART Interface | #define | Default pins for | Default pins for | External RS485 Driver Pin |
|
||||
| | | ESP32 (C6) | ESP32-S2 (S3, C3, C2, H2) | |
|
||||
| ----------------------|--------------------|-----------------------|---------------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
------------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information.
|
||||
|
||||
Connect a USB-to-RS485 adapter to a computer, then connect the adapter's A/B output lines with the corresponding A/B output lines of the RS485 line driver connected to the ESP32 chip (see figure above).
|
||||
|
||||
The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help string of parameters for more information.
|
||||
|
||||
### Setup external Modbus slave devices or emulator
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices.
|
||||
|
||||
Option 2:
|
||||
Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above.
|
||||
|
||||
### Build and flash software of master device
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (9035) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (9045) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.539999 (0x40b147ac) read successful.
|
||||
I (9045) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
I (9055) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful.
|
||||
I (9065) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (9075) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (9085) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (9095) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful.
|
||||
I (9605) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (9615) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.739999 (0x40b7ae12) read successful.
|
||||
I (9615) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
I (9625) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful.
|
||||
I (9635) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (9645) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (9655) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (9665) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful.
|
||||
I (10175) MASTER_TEST: Alarm triggered by cid #7.
|
||||
I (10175) MASTER_TEST: Destroy master...
|
||||
|
||||
```
|
||||
The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex).
|
||||
|
26
examples/serial/mb_serial_master/component.mk
Normal file
26
examples/serial/mb_serial_master/component.mk
Normal file
@@ -0,0 +1,26 @@
|
||||
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) \
|
||||
)
|
4
examples/serial/mb_serial_master/main/CMakeLists.txt
Normal file
4
examples/serial/mb_serial_master/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_serial_master")
|
||||
|
||||
idf_component_register(SRCS "serial_master.c"
|
||||
INCLUDE_DIRS ".")
|
99
examples/serial/mb_serial_master/main/Kconfig.projbuild
Normal file
99
examples/serial/mb_serial_master/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,99 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_ONE
|
||||
bool
|
||||
default y
|
||||
depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1)
|
||||
|
||||
config MB_UART_PORT_TWO
|
||||
bool
|
||||
default y
|
||||
depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2)
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if MB_UART_PORT_TWO
|
||||
default 2 if MB_UART_PORT_TWO
|
||||
range 0 1 if MB_UART_PORT_ONE
|
||||
default 1 if MB_UART_PORT_ONE
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 9 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART TX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_RTS
|
||||
int "UART RTS pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6
|
||||
default 20 if IDF_TARGET_ESP32P4
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART RTS pin. This pin is connected to
|
||||
~RE/DE pin of RS485 transceiver to switch direction.
|
||||
See UART documentation for more information about available pin
|
||||
numbers for UART.
|
||||
|
||||
choice MB_COMM_MODE
|
||||
prompt "Modbus communication mode"
|
||||
default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN
|
||||
help
|
||||
Selection of Modbus communication mode option for Modbus.
|
||||
|
||||
config MB_COMM_MODE_RTU
|
||||
bool "RTU mode"
|
||||
depends on FMB_COMM_MODE_RTU_EN
|
||||
|
||||
config MB_COMM_MODE_ASCII
|
||||
bool "ASCII mode"
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
7
examples/serial/mb_serial_master/main/idf_component.yml
Normal file
7
examples/serial/mb_serial_master/main/idf_component.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
idf: ">=5.0"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0.0"
|
||||
override_path: "../../../../"
|
||||
mb_example_common:
|
||||
path: "../../../mb_example_common"
|
499
examples/serial/mb_serial_master/main/serial_master.c
Normal file
499
examples/serial/mb_serial_master/main/serial_master.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "string.h"
|
||||
#include "esp_log.h"
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "mbcontroller.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection
|
||||
#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART
|
||||
|
||||
// Note: Some pins on target chip cannot be assigned for UART communication.
|
||||
// See UART documentation for selected board and target to configure pins using Kconfig.
|
||||
|
||||
// The number of parameters that intended to be used in the particular control process
|
||||
#define MASTER_MAX_CIDS num_device_parameters
|
||||
|
||||
// Number of reading of parameters from slave
|
||||
#define MASTER_MAX_RETRY 30
|
||||
|
||||
// Timeout to update cid over Modbus
|
||||
#define UPDATE_CIDS_TIMEOUT_MS (500)
|
||||
#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
|
||||
// Timeout between polls
|
||||
#define POLL_TIMEOUT_MS (1)
|
||||
#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
|
||||
// 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))
|
||||
// Discrete offset macro
|
||||
#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
|
||||
|
||||
#define STR(fieldname) ((const char *)( fieldname ))
|
||||
#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)
|
||||
|
||||
#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)
|
||||
|
||||
// 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++)
|
||||
|
||||
static const char *TAG = "MASTER_TEST";
|
||||
|
||||
// Enumeration of modbus device addresses accessed by master device
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here)
|
||||
};
|
||||
|
||||
// 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:
|
||||
// The CID field in the table must be unique.
|
||||
// Modbus Slave Addr field defines slave address of the device with correspond parameter.
|
||||
// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
|
||||
// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
|
||||
// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
|
||||
// Data Type, Data Size specify type of the characteristic and its data size.
|
||||
// Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
|
||||
// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
|
||||
const mb_parameter_descriptor_t device_parameters[] = {
|
||||
// { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
|
||||
{ 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_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_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
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
|
||||
|
||||
static void *master_handle = NULL;
|
||||
|
||||
// The function to get pointer to parameter storage (instance) according to parameter description table
|
||||
static 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", (unsigned)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, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(uint16_t)pdescr->param_opts.opt3); \
|
||||
is_correct = false; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (!is_correct) { \
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
|
||||
(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, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(int)err, \
|
||||
(char *)esp_err_to_name(err)); \
|
||||
} else { \
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(char *)pdescr->param_units); \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
|
||||
(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
|
||||
static void master_operation_func(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
bool alarm_state = false;
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
|
||||
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++) {
|
||||
// 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
|
||||
err = mbc_master_get_cid_info(master_handle, cid, ¶m_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)) {
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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) {
|
||||
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.",
|
||||
(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)) {
|
||||
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint8_t *)temp_data_ptr) == 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, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
|
||||
(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.",
|
||||
(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
|
||||
}
|
||||
}
|
||||
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
|
||||
}
|
||||
|
||||
if (alarm_state) {
|
||||
ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Alarm is not triggered after %u retries.",
|
||||
MASTER_MAX_RETRY);
|
||||
}
|
||||
ESP_LOGI(TAG, "Destroy master...");
|
||||
ESP_ERROR_CHECK(mbc_master_delete(master_handle));
|
||||
}
|
||||
|
||||
// Modbus master initialization
|
||||
static esp_err_t master_init(void)
|
||||
{
|
||||
// Initialize Modbus controller
|
||||
mb_communication_info_t comm = {
|
||||
.ser_opts.port = MB_PORT_NUM,
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
.ser_opts.mode = MB_RTU,
|
||||
#endif
|
||||
.ser_opts.baudrate = MB_DEV_SPEED,
|
||||
.ser_opts.parity = MB_PARITY_NONE,
|
||||
.ser_opts.uid = 0,
|
||||
.ser_opts.response_tout_ms = 1000,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1
|
||||
};
|
||||
|
||||
esp_err_t err = mbc_master_create_serial(&comm, &master_handle);
|
||||
MB_RETURN_ON_FALSE((master_handle != NULL), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller initialization fail.");
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller initialization fail, returns(0x%x).", (int)err);
|
||||
|
||||
// Set UART pin numbers
|
||||
err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
|
||||
CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err);
|
||||
|
||||
err = mbc_master_start(master_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller start fail, returned (0x%x).", (int)err);
|
||||
|
||||
// Set driver mode to Half Duplex
|
||||
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err);
|
||||
|
||||
vTaskDelay(5);
|
||||
err = mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller set descriptor fail, returns(0x%x).", (int)err);
|
||||
ESP_LOGI(TAG, "Modbus master stack initialized...");
|
||||
return err;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialization of device peripheral and objects
|
||||
ESP_ERROR_CHECK(master_init());
|
||||
vTaskDelay(10);
|
||||
|
||||
master_operation_func(NULL);
|
||||
}
|
7
examples/serial/mb_serial_master/sdkconfig.ci.ascii
Normal file
7
examples/serial/mb_serial_master/sdkconfig.ci.ascii
Normal file
@@ -0,0 +1,7 @@
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_COMM_MODE_RTU=n
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=y
|
7
examples/serial/mb_serial_master/sdkconfig.ci.rtu
Normal file
7
examples/serial/mb_serial_master/sdkconfig.ci.rtu
Normal file
@@ -0,0 +1,7 @@
|
||||
CONFIG_MB_COMM_MODE_ASCII=n
|
||||
CONFIG_MB_COMM_MODE_RTU=y
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=y
|
10
examples/serial/mb_serial_slave/CMakeLists.txt
Normal file
10
examples/serial/mb_serial_slave/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(modbus_serial_slave)
|
97
examples/serial/mb_serial_slave/README.md
Normal file
97
examples/serial/mb_serial_slave/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus Slave Example
|
||||
|
||||
This example demonstrates using the port of the esp-modbus stack on an ESP32 target where the ESP32 target is operating as a network slave. The example allows an external Modbus host to read/write device parameters on the ESP32 target using the Modbus protocol. The parameters accessible through Modbus are located in `mb_example_common/modbus_params.h\c` source/header files that users can update to add/remove their own custom parameters.
|
||||
These are represented in structures `holding_reg_params`, `input_reg_params`, `coil_reg_params`, `discrete_reg_params` for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
The FreeModbus stack located in `modbus` folder and supports the ESP32 targets. There are some parameters of the port that can be configured in KConfig file to start stack correctly (See description below for more information).
|
||||
|
||||
The slave example uses shared parameter structures defined in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board.
|
||||
The MAX485 line driver is used as an example below but other similar chips can be used as well.
|
||||
|
||||
Option 2:
|
||||
The modbus_master example application configured as described in its README.md file and flashed into ESP32 based board.
|
||||
Note: The ```Example Data (Object) Dictionary``` in the modbus_master example can be edited to address parameters from other slaves connected into Modbus segment.
|
||||
|
||||
RS485 example circuit schematic:
|
||||
```
|
||||
VCC ---------------+ +--------------- VCC
|
||||
| |
|
||||
+-------x-------+ +-------x-------+
|
||||
RXD <------| RO | DIFFERENTIAL | RO|-----> RXD
|
||||
| B|---------------|B |
|
||||
TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD
|
||||
ESP32 board | | RS-485 side | | Modbus master
|
||||
RTS --+--->| DE | / \ | DE|---+
|
||||
| | A|---------------|A | |
|
||||
+----| /RE | PAIR | /RE|---+-- RTS
|
||||
+-------x--------+ +-------x-------+
|
||||
| |
|
||||
--- ---
|
||||
```
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to show the configuration menu:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
Select Modbus Example Configuration menu item.
|
||||
Configure the UART pins used for modbus communication using the command and table below.
|
||||
```
|
||||
------------------------------------------------------------------------------------------------------------------------------
|
||||
| UART Interface | #define | Default pins for | Default pins for | External RS485 Driver Pin |
|
||||
| | | ESP32 (C6) | ESP32-S2 (S3, C3, C2, H2) | |
|
||||
| ----------------------|--------------------|-----------------------|---------------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
------------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information.
|
||||
|
||||
Define the ```Modbus communiction mode``` for slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave application).
|
||||
Set ```Modbus slave address``` for the example application (by default for example script is set to 1).
|
||||
The communication parameters of esp-modbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help strings of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
Option 2:
|
||||
Setup ESP32 based board and set modbus_master example configuration as described in its README.md file.
|
||||
Setup one or more slave boards with different slave addresses and connect them into the same Modbus segment (See configuration above).
|
||||
Note: The ```Modbus communiction mode``` parameter must be the same for master and slave example application to be able to communicate with each other.
|
||||
|
||||
### Build and flash software
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (13941) SLAVE_TEST: INPUT READ (13651163 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffb2fd0, SIZE:2
|
||||
I (13951) SLAVE_TEST: HOLDING READ (13656431 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2fe0, SIZE:2
|
||||
I (13961) SLAVE_TEST: INPUT READ (13665877 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffb2fd4, SIZE:2
|
||||
I (13971) SLAVE_TEST: HOLDING READ (13676010 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffb2fe4, SIZE:2
|
||||
I (13981) SLAVE_TEST: INPUT READ (13686130 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffb2fd8, SIZE:2
|
||||
I (13991) SLAVE_TEST: HOLDING READ (13696267 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffb2fe8, SIZE:2
|
||||
I (14001) SLAVE_TEST: COILS READ (13706331 us), ADDR:0, TYPE:32, INST_ADDR:0x3ffb2fcc, SIZE:8
|
||||
I (14001) SLAVE_TEST: Modbus controller destroyed.
|
||||
```
|
||||
The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly.
|
||||
|
4
examples/serial/mb_serial_slave/main/CMakeLists.txt
Normal file
4
examples/serial/mb_serial_slave/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_serial_slave")
|
||||
|
||||
idf_component_register(SRCS "serial_slave.c"
|
||||
INCLUDE_DIRS ".")
|
108
examples/serial/mb_serial_slave/main/Kconfig.projbuild
Normal file
108
examples/serial/mb_serial_slave/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,108 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_ONE
|
||||
bool
|
||||
default y
|
||||
depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1)
|
||||
|
||||
config MB_UART_PORT_TWO
|
||||
bool
|
||||
default y
|
||||
depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2)
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if MB_UART_PORT_TWO
|
||||
default 2 if MB_UART_PORT_TWO
|
||||
range 0 1 if MB_UART_PORT_ONE
|
||||
default 1 if MB_UART_PORT_ONE
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 9 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART TX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_RTS
|
||||
int "UART RTS pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 23 if IDF_TARGET_ESP32C6
|
||||
range 0 56 if IDF_TARGET_ESP32P4
|
||||
default 20 if IDF_TARGET_ESP32P4
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
range 0 20 if IDF_TARGET_ESP32C2
|
||||
range 0 27 if IDF_TARGET_ESP32H2
|
||||
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
|
||||
help
|
||||
GPIO number for UART RTS pin. This pin is connected to
|
||||
~RE/DE pin of RS485 transceiver to switch direction.
|
||||
See UART documentation for more information about available pin
|
||||
numbers for UART.
|
||||
|
||||
choice MB_COMM_MODE
|
||||
prompt "Modbus communication mode"
|
||||
default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN
|
||||
help
|
||||
Selection of Modbus communication mode option for Modbus.
|
||||
|
||||
config MB_COMM_MODE_RTU
|
||||
bool "RTU mode"
|
||||
depends on FMB_COMM_MODE_RTU_EN
|
||||
|
||||
config MB_COMM_MODE_ASCII
|
||||
bool "ASCII mode"
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
|
||||
endchoice
|
||||
|
||||
config MB_SLAVE_ADDR
|
||||
int "Modbus slave address"
|
||||
range 1 247
|
||||
default 1
|
||||
help
|
||||
This is the Modbus slave address in the network.
|
||||
It is used to organize Modbus network with several slaves connected into the same segment.
|
||||
|
||||
|
||||
endmenu
|
7
examples/serial/mb_serial_slave/main/idf_component.yml
Normal file
7
examples/serial/mb_serial_slave/main/idf_component.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
idf: ">=5.0"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0.0"
|
||||
override_path: "../../../../"
|
||||
mb_example_common:
|
||||
path: "../../../mb_example_common"
|
283
examples/serial/mb_serial_slave/main/serial_slave.c
Normal file
283
examples/serial/mb_serial_slave/main/serial_slave.c
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "esp_log.h" // for log_write
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection
|
||||
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR) // The address of device in Modbus network
|
||||
#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART
|
||||
|
||||
// Note: Some pins on target chip cannot be assigned for UART communication.
|
||||
// Please refer to documentation for selected board and target to configure pins using Kconfig.
|
||||
|
||||
// Defines below are used to define register start address for each type of Modbus registers
|
||||
#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 MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0
|
||||
#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1
|
||||
#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0))
|
||||
#define MB_REG_HOLDING_START_AREA0_SIZE ((size_t)((HOLD_OFFSET(holding_data4) - HOLD_OFFSET(holding_data0)) << 1))
|
||||
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
|
||||
#define MB_REG_HOLDING_START_AREA1_SIZE ((size_t)((HOLD_OFFSET(holding_area1_end) - HOLD_OFFSET(holding_data4)) << 1))
|
||||
#define MB_REG_HOLDING_START_AREA2 (HOLD_OFFSET(holding_u8_a))
|
||||
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET(holding_area2_end) - HOLD_OFFSET(holding_u8_a)) << 1))
|
||||
|
||||
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
|
||||
#define MB_CHAN_DATA_MAX_VAL (6)
|
||||
#define MB_CHAN_DATA_OFFSET (1.2f)
|
||||
#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
|
||||
|
||||
static const char *TAG = "SLAVE_TEST";
|
||||
|
||||
static void *mbc_slave_handle = 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 = 1.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;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on esp-modbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main(void)
|
||||
{
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(TAG, ESP_LOG_INFO);
|
||||
|
||||
// Initialize Modbus controller
|
||||
mb_communication_info_t comm_config = {
|
||||
.ser_opts.port = MB_PORT_NUM,
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
.ser_opts.mode = MB_RTU,
|
||||
#endif
|
||||
.ser_opts.baudrate = MB_DEV_SPEED,
|
||||
.ser_opts.parity = MB_PARITY_NONE,
|
||||
.ser_opts.uid = MB_SLAVE_ADDR,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(mbc_slave_create_serial(&comm_config, &mbc_slave_handle)); // Initialization of Modbus controller
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbc_slave_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register 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
|
||||
// Set the size of register storage instance in bytes
|
||||
reg_area.size = MB_REG_HOLDING_START_AREA0_SIZE;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_handle, reg_area));
|
||||
|
||||
// The second register area
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START_AREA1;
|
||||
reg_area.address = (void*)&holding_reg_params.holding_data4;
|
||||
reg_area.size = MB_REG_HOLDING_START_AREA1_SIZE;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_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;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_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;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_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;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_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);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_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);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_handle, reg_area));
|
||||
|
||||
setup_reg_data(); // Set values into known state
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
ESP_ERROR_CHECK(mbc_slave_start(mbc_slave_handle));
|
||||
|
||||
// Set UART pin numbers
|
||||
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD,
|
||||
CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS,
|
||||
UART_PIN_NO_CHANGE));
|
||||
|
||||
// Set UART driver mode to Half Duplex
|
||||
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
|
||||
|
||||
ESP_LOGI(TAG, "Modbus slave stack initialized.");
|
||||
ESP_LOGI(TAG, "Start modbus test...");
|
||||
|
||||
// The cycle below will be terminated when parameter holdingRegParams.dataChan0
|
||||
// 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(mbc_slave_handle, MB_READ_WRITE_MASK);
|
||||
// Get parameter information from parameter queue
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(mbc_slave_handle, ®_info, MB_PAR_INFO_GET_TOUT));
|
||||
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
|
||||
// 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",
|
||||
rw_str,
|
||||
reg_info.time_stamp,
|
||||
(unsigned)reg_info.mb_offset,
|
||||
(unsigned)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(unsigned)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
(void)mbc_slave_lock(mbc_slave_handle);
|
||||
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
|
||||
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
|
||||
coil_reg_params.coils_port1 = 0xFF;
|
||||
}
|
||||
(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",
|
||||
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",
|
||||
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",
|
||||
rw_str,
|
||||
reg_info.time_stamp,
|
||||
(unsigned)reg_info.mb_offset,
|
||||
(unsigned)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(unsigned)reg_info.size);
|
||||
if (coil_reg_params.coils_port1 == 0xFF) break;
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller on alarm
|
||||
ESP_LOGI(TAG,"Modbus controller destroyed.");
|
||||
vTaskDelay(100);
|
||||
ESP_ERROR_CHECK(mbc_slave_delete(mbc_slave_handle));
|
||||
}
|
6
examples/serial/mb_serial_slave/sdkconfig.ci.ascii
Normal file
6
examples/serial/mb_serial_slave/sdkconfig.ci.ascii
Normal file
@@ -0,0 +1,6 @@
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_COMM_MODE_RTU=n
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=y
|
6
examples/serial/mb_serial_slave/sdkconfig.ci.rtu
Normal file
6
examples/serial/mb_serial_slave/sdkconfig.ci.rtu
Normal file
@@ -0,0 +1,6 @@
|
||||
CONFIG_MB_COMM_MODE_ASCII=n
|
||||
CONFIG_MB_COMM_MODE_RTU=y
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=y
|
8
examples/serial/mb_serial_slave/sdkconfig.defaults
Normal file
8
examples/serial/mb_serial_slave/sdkconfig.defaults
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=y
|
71
examples/serial/pytest_mb_master_slave.py
Normal file
71
examples/serial/pytest_mb_master_slave.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# SPDX-FileCopyrightText: 2016-2023 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
|
||||
|
||||
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})'),
|
||||
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})'),
|
||||
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol 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})'),
|
||||
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
|
||||
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]+'),
|
||||
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).')}
|
||||
|
||||
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})'),
|
||||
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})'),
|
||||
Stages.STACK_INIT: (r'I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized)'),
|
||||
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling'),
|
||||
Stages.STACK_START: (r'I \(([0-9]+)\) MASTER_TEST: (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'),
|
||||
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]+\)'),
|
||||
Stages.STACK_DESTROY: (r'I \(([0-9]+)\) MASTER_TEST: (Destroy master)...')}
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = 'modbus_test'
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
test_configs = [
|
||||
'rtu',
|
||||
'ascii'
|
||||
]
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.multi_dut_modbus_serial
|
||||
@pytest.mark.parametrize('config', test_configs, indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
'count, app_path', [
|
||||
(2, f'{os.path.join(os.path.dirname(__file__), "mb_serial_master")}|{os.path.join(os.path.dirname(__file__), "mb_serial_slave")}')
|
||||
],
|
||||
indirect=True
|
||||
)
|
||||
def test_modbus_serial_communication(config: str, dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
|
||||
dut_slave = dut[1]
|
||||
dut_master = dut[0]
|
||||
|
||||
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)
|
||||
|
||||
dut_slave.dut_check_errors()
|
||||
dut_master.dut_check_errors()
|
||||
|
||||
@pytest.mark.multi_dut_modbus_generic
|
||||
@pytest.mark.parametrize('config', ['dummy_config'])
|
||||
def test_modbus_serial_generic() -> None:
|
||||
print('The generic serial example tests are not provided yet.')
|
58
examples/tcp/README.md
Normal file
58
examples/tcp/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
|
||||
|
8
examples/tcp/mb_tcp_master/CMakeLists.txt
Normal file
8
examples/tcp/mb_tcp_master/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
|
||||
set(EXCLUDE_COMPONENTS freemodbus)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_tcp_master)
|
146
examples/tcp/mb_tcp_master/README.md
Normal file
146
examples/tcp/mb_tcp_master/README.md
Normal file
@@ -0,0 +1,146 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus TCP Master Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32 targets as a TCP master device.
|
||||
This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file.
|
||||
The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment.
|
||||
The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of parameter exceeded limit.
|
||||
The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
Example parameters definition:
|
||||
--------------------------------------------------------------------------------------------------
|
||||
| Slave Address | Characteristic ID | Characteristic name | Description |
|
||||
|---------------------|----------------------|----------------------|----------------------------|
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off |
|
||||
--------------------------------------------------------------------------------------------------
|
||||
Note: The Slave Address is the same for all parameters for example test but it can be changed in the `Example Data (Object) Dictionary` table of master example to address parameters from other slaves.
|
||||
The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment.
|
||||
|
||||
Simplified Modbus connection schematic for example test:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
------------- -------------
|
||||
| | Network | |
|
||||
| Slave 1 |---<>--+---<>---| Master |
|
||||
| | | |
|
||||
------------- -------------
|
||||
```
|
||||
Modbus multi slave segment connection schematic:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
-------------
|
||||
| |
|
||||
| Slave 1 |---<>--+
|
||||
| | |
|
||||
------------- |
|
||||
MB_DEVICE_ADDR2 |
|
||||
------------- | -------------
|
||||
| | | | |
|
||||
| Slave 2 |---<>--+---<>---| Master |
|
||||
| | | | |
|
||||
------------- | -------------
|
||||
MB_DEVICE_ADDR3 |
|
||||
------------- Network (Ethernet or WiFi connection)
|
||||
| | |
|
||||
| Slave 3 |---<>--+
|
||||
| |
|
||||
-------------
|
||||
```
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC (Modbus TCP Slave application) + ESP32 based development board with modbus_tcp_slave example.
|
||||
|
||||
Option 2:
|
||||
Several ESP32 based boards flashed with modbus_tcp_slave example software to represent slave devices. The IP slave addresses for each board have to be configured in `Modbus Example Configuration` menu according to the communication table of example.
|
||||
One ESP32 based development board should be flashed with modbus_master example and connected to the same network. All the boards require configuration of network settings as described in `examples/common_components/protocol_examples_common`.
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to setup configuration:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help string of parameters for more information.
|
||||
There are three ways to configure how the master example will obtain slave IP addresses in the network:
|
||||
* Enable CONFIG_MB_MDNS_IP_RESOLVER option allows to query for modbus services provided by Modbus slaves in the network and automatically configure IP table. This requires to activate the same option for each slave with unique modbus slave address configured in `Modbus Example Configuration` menu.
|
||||
* Enable CONFIG_MB_SLAVE_IP_FROM_STDIN option to define IP addresses of slaves manually. In order to enter the IP addresses wait for the prompt and type the string with IP address following format. Prompt: "Waiting IPN from stdin:", then enter the IP address of the slave to connect: "IPN=192.168.1.21", where N = (configured slave address - 1).
|
||||
* Configure slave addresses manually as below:
|
||||
```
|
||||
char* slave_ip_address_table[MB_DEVICE_COUNT] = {
|
||||
"192.168.1.21", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
|
||||
"192.168.1.22", // Address corresponds to MB_DEVICE_ADDR2 of slave device in the Modbus data dictionary
|
||||
NULL // Marker of end of list
|
||||
};
|
||||
```
|
||||
|
||||
### Setup external Modbus slave devices or emulator
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices.
|
||||
|
||||
Option 2:
|
||||
Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above.
|
||||
|
||||
### Build and flash software of master device
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (4644) esp_netif_handlers: example_connect: sta ip: 192.168.1.39, mask: 255.255.255.0, gw: 192.168.1.1
|
||||
I (4644) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.39
|
||||
I (5644) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:bedd:c2ff:fed1:b210, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (5644) example_connect: Connected to example_connect: sta
|
||||
I (5654) example_connect: - IPv4 address: 192.168.1.39
|
||||
I (5664) example_connect: - IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed1:b210, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (5674) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
|
||||
I (5684) MASTER_TEST: Leave IP(0) = [192.168.1.21] set by user.
|
||||
I (5694) MASTER_TEST: IP(1) is not set in the table.
|
||||
I (5694) MASTER_TEST: Configured 1 IP addresse(s).
|
||||
I (5704) MASTER_TEST: Modbus master stack initialized...
|
||||
I (5704) MB_TCP_MASTER_PORT: TCP master stack initialized.
|
||||
I (5724) MB_TCP_MASTER_PORT: Host[IP]: "192.168.1.21"[192.168.1.21]
|
||||
I (5724) MB_TCP_MASTER_PORT: Add slave IP: 192.168.1.21
|
||||
I (5734) MB_TCP_MASTER_PORT: Connecting to slaves...
|
||||
-.-.-.I (5844) MB_TCP_MASTER_PORT: Connected 1 slaves, start polling...
|
||||
I (6004) MASTER_TEST: Start modbus test...
|
||||
I (6044) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (6054) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 1.340000 (0x3fab851f) read successful.
|
||||
I (6074) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
I (6084) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful.
|
||||
I (6094) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (6104) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (6124) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (6134) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful.
|
||||
I (6854) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (7064) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 1.740000 (0x3fdeb852) read successful.
|
||||
I (7264) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
...
|
||||
I (45974) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (46174) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (46384) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (46584) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful.
|
||||
I (47094) MASTER_TEST: Alarm triggered by cid #7.
|
||||
I (47094) MASTER_TEST: Destroy master...
|
||||
```
|
||||
The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex data).
|
||||
|
4
examples/tcp/mb_tcp_master/main/CMakeLists.txt
Normal file
4
examples/tcp/mb_tcp_master/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_tcp_master")
|
||||
|
||||
idf_component_register(SRCS "tcp_master.c"
|
||||
INCLUDE_DIRS ".")
|
16
examples/tcp/mb_tcp_master/main/Kconfig.projbuild
Normal file
16
examples/tcp/mb_tcp_master/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,16 @@
|
||||
menu "Modbus TCP Example Configuration"
|
||||
|
||||
choice MB_SLAVE_IP_RESOLVER
|
||||
prompt "Select method to resolve slave IP addresses"
|
||||
help
|
||||
Select method which is used to resolve slave IP addresses
|
||||
and configure Master TCP IP stack.
|
||||
|
||||
config MB_MDNS_IP_RESOLVER
|
||||
bool "Resolve Modbus slave addresses using mDNS service."
|
||||
|
||||
config MB_SLAVE_IP_FROM_STDIN
|
||||
bool "Configure Modbus slave addresses from stdin"
|
||||
endchoice
|
||||
|
||||
endmenu
|
12
examples/tcp/mb_tcp_master/main/idf_component.yml
Normal file
12
examples/tcp/mb_tcp_master/main/idf_component.yml
Normal file
@@ -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: "../../../mb_example_common"
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
726
examples/tcp/mb_tcp_master/main/tcp_master.c
Normal file
726
examples/tcp/mb_tcp_master/main/tcp_master.c
Normal file
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// esp-modbus Master Example ESP32
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/queue.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_mac.h"
|
||||
|
||||
#include "mdns.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "mbcontroller.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#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
|
||||
|
||||
// Number of reading of parameters from slave
|
||||
#define MASTER_MAX_RETRY (10)
|
||||
|
||||
// Timeout to update cid over Modbus
|
||||
#define UPDATE_CIDS_TIMEOUT_MS (500)
|
||||
#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
|
||||
// Timeout between polls
|
||||
#define POLL_TIMEOUT_MS (1)
|
||||
#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
#define MB_MDNS_PORT (502)
|
||||
|
||||
// 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 STR(fieldname) ((const char *)( fieldname ))
|
||||
#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)
|
||||
|
||||
#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)
|
||||
|
||||
// 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 MB_ID_BYTE0(id) ((uint8_t)(id))
|
||||
#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
|
||||
#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
|
||||
#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
|
||||
|
||||
#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
|
||||
|
||||
#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
|
||||
#else
|
||||
#define MB_DEVICE_ID (uint32_t)0x00112233
|
||||
#endif
|
||||
|
||||
#define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
|
||||
|
||||
#define EACH_ITEM(array, length) \
|
||||
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
|
||||
|
||||
static const char *TAG = "MASTER_TEST";
|
||||
|
||||
// Enumeration of modbus device addresses accessed by master device
|
||||
// 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
|
||||
};
|
||||
|
||||
// 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:
|
||||
// The CID field in the table must be unique.
|
||||
// Modbus Slave Addr field defines slave address of the device with correspond parameter.
|
||||
// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
|
||||
// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
|
||||
// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
|
||||
// Data Type, Data Size specify type of the characteristic and its data size.
|
||||
// Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
|
||||
// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
|
||||
const mb_parameter_descriptor_t device_parameters[] = {
|
||||
// { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
|
||||
{ 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_ADDR2, 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,
|
||||
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
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
|
||||
|
||||
static void* master_handle = NULL;
|
||||
|
||||
const size_t ip_table_sz;
|
||||
|
||||
// This table represents slave IP addresses that correspond to the short address field of the slave in device_parameters structure
|
||||
// Modbus TCP stack shall use these addresses to be able to connect and read parameters from slave
|
||||
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
|
||||
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",
|
||||
NULL // End of table condition (must be included)
|
||||
#endif
|
||||
};
|
||||
|
||||
const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0]));
|
||||
|
||||
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
|
||||
// Scan IP address according to IPV settings
|
||||
char *master_scan_addr(int *index, char *buffer)
|
||||
{
|
||||
char *ip_str = NULL;
|
||||
int a[8] = {0};
|
||||
int buf_cnt = 0;
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
buf_cnt = sscanf(buffer, "IP%d=" IPSTR, index, &a[0], &a[1], &a[2], &a[3]);
|
||||
if (buf_cnt == 5) {
|
||||
if (-1 == asprintf(&ip_str, "%02x;" IPSTR, (int)(*index + 1), a[0], a[1], a[2], a[3])) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#else
|
||||
buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
|
||||
if (buf_cnt == 9) {
|
||||
if (-1 == asprintf(&ip_str, "%02x;" IPV6STR, (int)(*index + 1), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
printf("IP string: %s", ip_str);
|
||||
return ip_str;
|
||||
}
|
||||
|
||||
static int master_get_slave_ip_stdin(char **addr_table)
|
||||
{
|
||||
char buf[128];
|
||||
int index;
|
||||
char *ip_str = NULL;
|
||||
int buf_cnt = 0;
|
||||
int ip_cnt = 0;
|
||||
|
||||
if (!addr_table) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
while(1) {
|
||||
if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) {
|
||||
printf("Waiting IP%d from stdin:\r\n", (int)ip_cnt);
|
||||
while (fgets(buf, sizeof(buf), stdin) == NULL) {
|
||||
fputs(buf, stdout);
|
||||
}
|
||||
buf_cnt = strlen(buf);
|
||||
buf[buf_cnt - 1] = '\0';
|
||||
fputc('\n', stdout);
|
||||
ip_str = master_scan_addr(&index, buf);
|
||||
if (ip_str != NULL) {
|
||||
ESP_LOGI(TAG, "IP(%d) = [%s] set from stdin.", (int)ip_cnt, ip_str);
|
||||
if ((ip_cnt >= ip_table_sz) || (index != ip_cnt)) {
|
||||
addr_table[ip_cnt] = NULL;
|
||||
break;
|
||||
}
|
||||
addr_table[ip_cnt++] = ip_str;
|
||||
} else {
|
||||
// End of configuration
|
||||
addr_table[ip_cnt++] = NULL;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (addr_table[ip_cnt]) {
|
||||
ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", (int)ip_cnt, addr_table[ip_cnt]);
|
||||
ip_cnt++;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "IP(%d) is not set in the table.", (int)ip_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ip_cnt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void master_destroy_slave_list(char **table, size_t ip_table_size)
|
||||
{
|
||||
for (int i = 0; ((i < ip_table_size) && table[i] != NULL); i++) {
|
||||
if (table[i]) {
|
||||
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
free(table[i]);
|
||||
table[i] = "FROM_STDIN";
|
||||
#elif CONFIG_MB_MDNS_IP_RESOLVER
|
||||
table[i] = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The function to get pointer to parameter storage (instance) according to parameter description table
|
||||
static 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, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(uint16_t)pdescr->param_opts.opt3); \
|
||||
is_correct = false; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (!is_correct) { \
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
|
||||
(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, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(int)err, \
|
||||
(char *)esp_err_to_name(err)); \
|
||||
} else { \
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
|
||||
(int)pdescr->cid, \
|
||||
(char *)pdescr->param_key, \
|
||||
(char *)pdescr->param_units); \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
|
||||
(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
|
||||
static void master_operation_func(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
bool alarm_state = false;
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
|
||||
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++) {
|
||||
// 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
|
||||
err = mbc_master_get_cid_info(master_handle, cid, ¶m_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)) {
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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
|
||||
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.",
|
||||
(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) {
|
||||
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.",
|
||||
(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)) {
|
||||
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint8_t *)temp_data_ptr) == 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, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
|
||||
(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.",
|
||||
(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
|
||||
}
|
||||
}
|
||||
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
|
||||
}
|
||||
|
||||
if (alarm_state) {
|
||||
ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Alarm is not triggered after %u retries.",
|
||||
MASTER_MAX_RETRY);
|
||||
}
|
||||
ESP_LOGI(TAG, "Destroy master...");
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
static esp_err_t init_services(mb_tcp_addr_type_t ip_addr_type)
|
||||
{
|
||||
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();
|
||||
}
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"nvs_flash_init fail, returns(0x%x).",
|
||||
(int)result);
|
||||
result = esp_netif_init();
|
||||
MB_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();
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"esp_event_loop_create_default fail, returns(0x%x).",
|
||||
(int)result);
|
||||
// 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();
|
||||
MB_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);
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"esp_wifi_set_ps fail, returns(0x%x).",
|
||||
(int)result);
|
||||
#endif
|
||||
|
||||
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table);
|
||||
if (ip_cnt) {
|
||||
ESP_LOGI(TAG, "Configured %d IP addresse(s).", ip_cnt);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Fail to get IP address from stdin. Continue.");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t destroy_services(void)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
master_destroy_slave_list(slave_ip_address_table, ip_table_sz);
|
||||
|
||||
err = example_disconnect();
|
||||
MB_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();
|
||||
MB_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();
|
||||
MB_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();
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"nvs_flash_deinit fail, returns(0x%x).",
|
||||
(int)err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Modbus master initialization
|
||||
static esp_err_t master_init(mb_communication_info_t *pcomm_info)
|
||||
{
|
||||
esp_err_t err = mbc_master_create_tcp(pcomm_info, &master_handle);
|
||||
MB_RETURN_ON_FALSE((master_handle != NULL), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mb controller initialization fail.");
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mb controller initialization fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
err = mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mb controller set descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
ESP_LOGI(TAG, "Modbus master stack initialized...");
|
||||
|
||||
err = mbc_master_start(master_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mb controller start fail, returns(0x%x).",
|
||||
(int)err);
|
||||
vTaskDelay(5);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t master_destroy(void)
|
||||
{
|
||||
esp_err_t err = mbc_master_delete(master_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_master_destroy fail, returns(0x%x).",
|
||||
(int)err);
|
||||
ESP_LOGI(TAG, "Modbus master stack destroy...");
|
||||
return err;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
mb_tcp_addr_type_t ip_addr_type;
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
ip_addr_type = MB_IPV4;
|
||||
#else
|
||||
ip_addr_type = MB_IPV6;
|
||||
#endif
|
||||
ESP_ERROR_CHECK(init_services(ip_addr_type));
|
||||
|
||||
mb_communication_info_t tcp_master_config = {
|
||||
.tcp_opts.port = MB_TCP_PORT,
|
||||
.tcp_opts.mode = MB_TCP,
|
||||
.tcp_opts.addr_type = ip_addr_type,
|
||||
.tcp_opts.ip_addr_table = (void *)slave_ip_address_table,
|
||||
.tcp_opts.uid = 0,
|
||||
.tcp_opts.start_disconnected = false,
|
||||
.tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND,
|
||||
.tcp_opts.ip_netif_ptr = (void*)get_example_netif()
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(master_init(&tcp_master_config));
|
||||
|
||||
master_operation_func(NULL);
|
||||
ESP_ERROR_CHECK(master_destroy());
|
||||
ESP_ERROR_CHECK(destroy_services());
|
||||
}
|
33
examples/tcp/mb_tcp_master/sdkconfig.ci.ethernet
Normal file
33
examples/tcp/mb_tcp_master/sdkconfig.ci.ethernet
Normal file
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# 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_MB_SLAVE_IP_FROM_STDIN=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_PHY_INTERFACE_RMII=y
|
||||
CONFIG_ETH_USE_SPI_ETHERNET=n
|
20
examples/tcp/mb_tcp_master/sdkconfig.ci.wifi
Normal file
20
examples/tcp/mb_tcp_master/sdkconfig.ci.wifi
Normal file
@@ -0,0 +1,20 @@
|
||||
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_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_FMB_TCP_UID_ENABLED=n
|
||||
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=n
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FMB_EXT_TYPE_SUPPORT=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}"
|
22
examples/tcp/mb_tcp_master/sdkconfig.defaults
Normal file
22
examples/tcp/mb_tcp_master/sdkconfig.defaults
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# 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=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_EXT_TYPE_SUPPORT=y
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=n
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
|
||||
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
|
||||
|
9
examples/tcp/mb_tcp_slave/CMakeLists.txt
Normal file
9
examples/tcp/mb_tcp_slave/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_tcp_slave)
|
85
examples/tcp/mb_tcp_slave/README.md
Normal file
85
examples/tcp/mb_tcp_slave/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus Slave Example
|
||||
The stack located in `modbus` folder and includes support for ESP32 target chips. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information). The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in `mb_example_common/modbus_params.h\c` files and can be updated by user.
|
||||
These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
|
||||
|
||||
The slave example uses shared parameter structures defined in ```examples/protocols/modbus/mb_example_common``` folder.
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
The ESP32 based development board flashed with modbus_tcp_slave example + external Modbus master host software.
|
||||
|
||||
Option 2:
|
||||
The modbus_tcp_master example application configured as described in its README.md file and flashed into ESP32 based board.
|
||||
Note: The ```Example Data (Object) Dictionary``` in the modbus_tcp_master example can be edited to address parameters from other slaves connected into Modbus segment.
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to show the configuration menu:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
To configure the example to use Wi-Fi or Ethernet connection, open the project configuration menu and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" in the "Connect using" choice.
|
||||
Follow the instructions in `examples/common_components/protocol_examples_common` for further configuration.
|
||||
|
||||
The communication parameters of esp-modbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help strings of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
Option 2:
|
||||
Setup ESP32 based development board and set modbus_tcp_master example configuration as described in its README.md file.
|
||||
Setup one or more slave boards and connect them into the same Modbus segment (See configuration above).
|
||||
|
||||
### Build and flash software
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (4235) esp_netif_handlers: example_connect: sta ip: 192.168.1.21, mask: 255.255.255.0, gw: 192.168.1.1
|
||||
I (4235) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.21
|
||||
I (4465) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:7edf:a1ff:fe00:4039, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (4465) example_connect: Connected to example_connect: sta
|
||||
I (4475) example_connect: - IPv4 address: 192.168.1.21
|
||||
I (4475) example_connect: - IPv6 address: fe80:0000:0000:0000:7edf:a1ff:fe00:4039, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (4495) MB_TCP_SLAVE_PORT: Socket (#54), listener on port: 502, errno=0
|
||||
I (4495) MB_TCP_SLAVE_PORT: Protocol stack initialized.
|
||||
I (4505) SLAVE_TEST: Modbus slave stack initialized.
|
||||
I (4505) SLAVE_TEST: Start modbus test...
|
||||
I (41035) MB_TCP_SLAVE_PORT: Socket (#55), accept client connection from address: 192.168.1.39
|
||||
I (41225) SLAVE_TEST: INPUT READ (41704766 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffcb878, SIZE:2
|
||||
I (41235) SLAVE_TEST: HOLDING READ (41719746 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffcb9b4, SIZE:2
|
||||
I (41255) SLAVE_TEST: INPUT READ (41732965 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffcb87c, SIZE:2
|
||||
I (41265) SLAVE_TEST: HOLDING READ (41745923 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffcb9b8, SIZE:2
|
||||
I (41275) SLAVE_TEST: INPUT READ (41759563 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffcb880, SIZE:2
|
||||
I (41295) SLAVE_TEST: HOLDING READ (41772568 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2
|
||||
I (41305) SLAVE_TEST: COILS WRITE (41785889 us), ADDR:0, TYPE:16, INST_ADDR:0x3ffcb874, SIZE:8
|
||||
I (41315) SLAVE_TEST: COILS WRITE (41799175 us), ADDR:8, TYPE:16, INST_ADDR:0x3ffcb875, SIZE:8
|
||||
I (41945) SLAVE_TEST: INPUT READ (42421629 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffcb878, SIZE:2
|
||||
I (42145) SLAVE_TEST: HOLDING READ (42626497 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffcb9b4, SIZE:2
|
||||
I (42345) SLAVE_TEST: INPUT READ (42831315 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffcb87c, SIZE:2
|
||||
I (42555) SLAVE_TEST: HOLDING READ (43036111 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffcb9b8, SIZE:2
|
||||
I (42755) SLAVE_TEST: INPUT READ (43240950 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffcb880, SIZE:2
|
||||
I (42865) SLAVE_TEST: HOLDING READ (43343204 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2
|
||||
......
|
||||
I (81265) SLAVE_TEST: HOLDING READ (81743698 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2
|
||||
I (81465) SLAVE_TEST: COILS WRITE (81948482 us), ADDR:0, TYPE:16, INST_ADDR:0x3ffcb874, SIZE:8
|
||||
I (81465) SLAVE_TEST: Modbus controller destroyed.
|
||||
```
|
||||
The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly.
|
||||
|
4
examples/tcp/mb_tcp_slave/main/CMakeLists.txt
Normal file
4
examples/tcp/mb_tcp_slave/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_tcp_slave")
|
||||
|
||||
idf_component_register(SRCS "tcp_slave.c"
|
||||
INCLUDE_DIRS ".")
|
12
examples/tcp/mb_tcp_slave/main/Kconfig.projbuild
Normal file
12
examples/tcp/mb_tcp_slave/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,12 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_SLAVE_ADDR
|
||||
int "Modbus slave address"
|
||||
range 1 247 if !FMB_TCP_UID_ENABLED
|
||||
range 0 247 if FMB_TCP_UID_ENABLED
|
||||
default 1
|
||||
help
|
||||
This is the Modbus slave address in the network.
|
||||
The address is used as an index to resolve slave ip address.
|
||||
|
||||
endmenu
|
11
examples/tcp/mb_tcp_slave/main/idf_component.yml
Normal file
11
examples/tcp/mb_tcp_slave/main/idf_component.yml
Normal file
@@ -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: "../../../mb_example_common"
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
415
examples/tcp/mb_tcp_slave/main/tcp_slave.c
Normal file
415
examples/tcp/mb_tcp_slave/main/tcp_slave.c
Normal file
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// FreeModbus Slave Example ESP32
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_err.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "mdns.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
#if __has_include("esp_mac.h")
|
||||
#include "esp_mac.h"
|
||||
#endif
|
||||
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
|
||||
#define MB_TCP_PORT_NUMBER (CONFIG_FMB_TCP_PORT_DEFAULT)
|
||||
|
||||
// Defines below are used to define register start address for each type of Modbus registers
|
||||
#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 MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0
|
||||
#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1
|
||||
#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0))
|
||||
#define MB_REG_HOLDING_START_AREA0_SIZE ((size_t)((HOLD_OFFSET(holding_data4) - HOLD_OFFSET(holding_data0)) << 1))
|
||||
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
|
||||
#define MB_REG_HOLDING_START_AREA1_SIZE ((size_t)((HOLD_OFFSET(holding_area1_end) - HOLD_OFFSET(holding_data4)) << 1))
|
||||
#define MB_REG_HOLDING_START_AREA2 (HOLD_OFFSET(holding_u8_a))
|
||||
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET(holding_area2_end) - HOLD_OFFSET(holding_u8_a)) << 1))
|
||||
|
||||
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
|
||||
#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)
|
||||
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
|
||||
|
||||
static const char *TAG = "SLAVE_TEST";
|
||||
|
||||
static void *slave_handle = 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 = 1.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;
|
||||
}
|
||||
|
||||
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, "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, ®_info, MB_PAR_INFO_GET_TOUT));
|
||||
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
// 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",
|
||||
rw_str,
|
||||
(unsigned)reg_info.time_stamp,
|
||||
(unsigned)reg_info.mb_offset,
|
||||
(unsigned)reg_info.type,
|
||||
(int)reg_info.address,
|
||||
(unsigned)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
(void)mbc_slave_unlock(slave_handle);
|
||||
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
|
||||
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
|
||||
coil_reg_params.coils_port1 = 0xFF;
|
||||
ESP_LOGI(TAG, "Riched maximum value");
|
||||
}
|
||||
(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",
|
||||
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",
|
||||
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",
|
||||
rw_str,
|
||||
reg_info.time_stamp,
|
||||
(unsigned)reg_info.mb_offset,
|
||||
(unsigned)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(unsigned)reg_info.size);
|
||||
if (coil_reg_params.coils_port1 == 0xFF) {
|
||||
ESP_LOGI(TAG, "Stop polling.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller on alarm
|
||||
ESP_LOGI(TAG,"Modbus controller destroyed.");
|
||||
vTaskDelay(100);
|
||||
}
|
||||
|
||||
static esp_err_t init_services(void)
|
||||
{
|
||||
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();
|
||||
}
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"nvs_flash_init fail, returns(0x%x).",
|
||||
(int)result);
|
||||
result = esp_netif_init();
|
||||
MB_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();
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"esp_event_loop_create_default fail, returns(0x%x).",
|
||||
(int)result);
|
||||
// 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();
|
||||
MB_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);
|
||||
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"esp_wifi_set_ps fail, returns(0x%x).",
|
||||
(int)result);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t destroy_services(void)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
err = example_disconnect();
|
||||
MB_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();
|
||||
MB_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();
|
||||
MB_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();
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"nvs_flash_deinit fail, returns(0x%x).",
|
||||
(int)err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Modbus slave initialization
|
||||
static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
|
||||
{
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
// Initialization of Modbus controller
|
||||
esp_err_t err = mbc_slave_create_tcp(pcomm_info, &slave_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK && slave_handle != NULL), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mb controller create fail.");
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbc_slave_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register 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
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
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
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
#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;
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
#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;
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
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;
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
// 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);
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
// 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);
|
||||
err = mbc_slave_set_descriptor(slave_handle, reg_area);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_set_descriptor fail, returns(0x%x).",
|
||||
(int)err);
|
||||
|
||||
// Set values into known state
|
||||
setup_reg_data();
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
err = mbc_slave_start(slave_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_start fail, returns(0x%x).",
|
||||
(int)err);
|
||||
vTaskDelay(5);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t slave_destroy(void)
|
||||
{
|
||||
esp_err_t err = mbc_slave_delete(slave_handle);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
TAG,
|
||||
"mbc_slave_destroy fail, returns(0x%x).",
|
||||
(int)err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on esp-modbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(init_services());
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(TAG, ESP_LOG_INFO);
|
||||
|
||||
mb_communication_info_t tcp_slave_config = {
|
||||
.tcp_opts.port = MB_TCP_PORT_NUMBER,
|
||||
.tcp_opts.mode = MB_TCP,
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
.tcp_opts.addr_type = MB_IPV4,
|
||||
#else
|
||||
.tcp_opts.addr_type = MB_IPV6,
|
||||
#endif
|
||||
.tcp_opts.ip_addr_table = NULL, // Bind to any address
|
||||
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(),
|
||||
.tcp_opts.uid = MB_SLAVE_ADDR
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(slave_init(&tcp_slave_config));
|
||||
|
||||
// The Modbus slave logic is located in this function (user handling of Modbus)
|
||||
slave_operation_func(NULL);
|
||||
|
||||
ESP_ERROR_CHECK(slave_destroy());
|
||||
ESP_ERROR_CHECK(destroy_services());
|
||||
}
|
32
examples/tcp/mb_tcp_slave/sdkconfig.ci.ethernet
Normal file
32
examples/tcp/mb_tcp_slave/sdkconfig.ci.ethernet
Normal file
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# 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_MB_SLAVE_ADDR=1
|
||||
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_PHY_INTERFACE_RMII=y
|
||||
CONFIG_ETH_USE_SPI_ETHERNET=n
|
19
examples/tcp/mb_tcp_slave/sdkconfig.ci.wifi
Normal file
19
examples/tcp/mb_tcp_slave/sdkconfig.ci.wifi
Normal file
@@ -0,0 +1,19 @@
|
||||
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_EXT_TYPE_SUPPORT=y
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_FMB_TCP_UID_ENABLED=n
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
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}"
|
21
examples/tcp/mb_tcp_slave/sdkconfig.defaults
Normal file
21
examples/tcp/mb_tcp_slave/sdkconfig.defaults
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# 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=2000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TCP_UID_ENABLED=n
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=n
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
|
||||
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=y
|
77
examples/tcp/pytest_mb_tcp_host_test_slave.py
Normal file
77
examples/tcp/pytest_mb_tcp_host_test_slave.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# SPDX-FileCopyrightText: 2016-2023 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,sys
|
||||
import subprocess
|
||||
|
||||
# pytest required libraries
|
||||
import pytest
|
||||
from conftest import ModbusTestDut, Stages
|
||||
|
||||
|
||||
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
TEST_ROBOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tools/robot'))
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = 'modbus_test'
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
if os.name == 'nt':
|
||||
CLOSE_FDS = False
|
||||
else:
|
||||
CLOSE_FDS = True
|
||||
|
||||
|
||||
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]+)\) MB_TCP_SLAVE_PORT: (Protocol 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})'),
|
||||
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
|
||||
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]+'),
|
||||
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).')}
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.multi_dut_modbus_tcp
|
||||
@pytest.mark.parametrize('config', ['ethernet'], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
'count, app_path', [
|
||||
(1, f'{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}')
|
||||
],
|
||||
indirect=True
|
||||
)
|
||||
def test_modbus_tcp_host_to_slave_communication(app_path, dut: ModbusTestDut) -> None:
|
||||
logger.info('DUT: %s start.', dut.dut_get_name())
|
||||
dut_slave_ip_address = dut.dut_get_ip()
|
||||
assert dut_slave_ip_address is not None, "The DUT could not get IP address. Abort."
|
||||
dut_slave_ip_port = dut.app.sdkconfig.get('FMB_TCP_PORT_DEFAULT')
|
||||
assert dut_slave_ip_port is not None, f"DUT port is not correct: {dut_slave_ip_port}"
|
||||
logger.info(f'Start test for the slave: {app_path}, {dut_slave_ip_address}:{dut_slave_ip_port}')
|
||||
try:
|
||||
cmd = 'robot ' + \
|
||||
f'--variable MODBUS_DEF_SERVER_IP:{dut_slave_ip_address} ' + \
|
||||
f'--variable MODBUS_DEF_SERVER_PORT:{dut_slave_ip_port} ' + \
|
||||
f'{TEST_ROBOT_DIR}/ModbusTestSuite.robot'
|
||||
p = subprocess.Popen(cmd,
|
||||
stdin=None, stdout=None, stderr=None,
|
||||
shell=True,
|
||||
close_fds=CLOSE_FDS
|
||||
)
|
||||
dut.dut_test_start(dictionary=pattern_dict_slave)
|
||||
p.wait()
|
||||
logger.info(f'Test for the node: {dut_slave_ip_address} is completed.')
|
||||
dut.dut_check_errors()
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error('robot framework fail with error: %d', e.returncode)
|
||||
logging.debug("Command ran: '%s'", e.cmd)
|
||||
logging.debug('Command out:')
|
||||
logging.debug(e.output)
|
||||
logging.error('Check the correctneess of the suite script.')
|
||||
raise e
|
77
examples/tcp/pytest_mb_tcp_master_slave.py
Normal file
77
examples/tcp/pytest_mb_tcp_master_slave.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# SPDX-FileCopyrightText: 2016-2023 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
|
||||
|
||||
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]+)\) MB_TCP_SLAVE_PORT: (Protocol 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})'),
|
||||
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
|
||||
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]+'),
|
||||
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).')}
|
||||
|
||||
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]+)\) MASTER_TEST: (Modbus master stack initialized)'),
|
||||
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling'),
|
||||
Stages.STACK_START: (r'I \(([0-9]+)\) MASTER_TEST: (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'),
|
||||
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]+\)'),
|
||||
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 = [
|
||||
'wifi',
|
||||
'ethernet'
|
||||
]
|
||||
|
||||
@pytest.mark.esp32
|
||||
@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_master")}|{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}')
|
||||
],
|
||||
indirect=True
|
||||
)
|
||||
def test_modbus_tcp_communication(dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
|
||||
dut_slave = dut[1]
|
||||
dut_master = dut[0]
|
||||
|
||||
logger.info('DUT: %s start.', dut_master.dut_get_name())
|
||||
logger.info('DUT: %s start.', dut_slave.dut_get_name())
|
||||
|
||||
dut_slave_ip_address = dut_slave.dut_get_ip()
|
||||
dut_master.dut_send_ip(dut_slave_ip_address)
|
||||
|
||||
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
|
||||
dut_master.dut_test_start(dictionary=pattern_dict_master)
|
||||
|
||||
dut_slave.dut_check_errors()
|
||||
dut_master.dut_check_errors()
|
||||
|
||||
@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.')
|
||||
|
||||
|
16
idf_component.yml
Normal file
16
idf_component.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
version: "2.0.1"
|
||||
description: ESP-MODBUS is the official Modbus library for Espressif SoCs.
|
||||
url: https://github.com/espressif/esp-modbus
|
||||
dependencies:
|
||||
idf: ">=5.0"
|
||||
files:
|
||||
exclude:
|
||||
- "docs/**/*"
|
||||
- "docs"
|
||||
- "test/**/*"
|
||||
- "test"
|
||||
- "pytest_embedded_log/**/*"
|
||||
- "build*/**/*"
|
||||
- "**/*.zip"
|
||||
- "__pycache__/**/*"
|
||||
|
23
linker.lf
Normal file
23
linker.lf
Normal file
@@ -0,0 +1,23 @@
|
||||
[mapping:esp_modbus]
|
||||
archive: libesp-modbus.a
|
||||
entries:
|
||||
* (default)
|
||||
port_event: mb_port_event_set_err_type (noflash_text)
|
||||
port_event: mb_port_event_post (noflash_text)
|
||||
port_other: lock_obj (noflash_text)
|
||||
port_other: unlock_obj (noflash_text)
|
||||
|
||||
if FMB_TIMER_USE_ISR_DISPATCH_METHOD = y:
|
||||
# tcp_master: mbm_tcp_transp_timer_expired (noflash_text)
|
||||
tcp_slave: mbs_tcp_transp_timer_expired (noflash_text)
|
||||
# port_tcp_slave: mbs_port_timer_expired (noflash_text)
|
||||
port_tcp_master: mbm_port_timer_expired (noflash_text)
|
||||
port_timer: timer_alarm_cb (noflash_text)
|
||||
port_timer: mb_port_set_cur_timer_mode (noflash_text)
|
||||
port_timer: mb_port_get_cur_timer_mode (noflash_text)
|
||||
port_timer: mb_port_timer_disable (noflash_text)
|
||||
ascii_master: mbm_ascii_transp_timer_expired (noflash_text)
|
||||
ascii_slave: mbs_ascii_transp_timer_expired (noflash_text)
|
||||
rtu_master: mbm_rtu_transp_timer_expired (noflash_text)
|
||||
rtu_slave: mbs_rtu_transp_timer_expired (noflash_text)
|
||||
|
24
modbus/mb_controller/common/esp_modbus_callbacks.h
Normal file
24
modbus/mb_controller/common/esp_modbus_callbacks.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Stack callback functions prototypes
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mb_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef mb_err_enum_t (*reg_input_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t);
|
||||
typedef mb_err_enum_t (*reg_holding_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t, mb_reg_mode_enum_t);
|
||||
typedef mb_err_enum_t (*reg_coils_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t, mb_reg_mode_enum_t);
|
||||
typedef mb_err_enum_t (*reg_discrete_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
716
modbus/mb_controller/common/esp_modbus_master.c
Normal file
716
modbus/mb_controller/common/esp_modbus_master.c
Normal file
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "mbc_master.h" // for master interface define
|
||||
#include "esp_modbus_master.h" // for public interface defines
|
||||
|
||||
static const char TAG[] __attribute__((unused)) = "MB_CONTROLLER_MASTER";
|
||||
|
||||
// This file implements public API for Modbus master controller.
|
||||
|
||||
/**
|
||||
* Modbus controller delete function
|
||||
*/
|
||||
esp_err_t mbc_master_delete(void *ctx)
|
||||
{
|
||||
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->delete, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
error = mbm_controller->delete (ctx);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error,
|
||||
TAG, "Master delete failure, error=(0x%x).", (uint16_t)error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical section lock function
|
||||
*/
|
||||
esp_err_t mbc_master_lock(void *ctx)
|
||||
{
|
||||
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_base_t *pmb_obj = (mb_base_t *)mbm_controller->mb_base;
|
||||
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
CRITICAL_SECTION_LOCK(pmb_obj->lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical section unlock function
|
||||
*/
|
||||
esp_err_t mbc_master_unlock(void *ctx)
|
||||
{
|
||||
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_base_t *pmb_obj = (mb_base_t *)mbm_controller->mb_base;
|
||||
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
CRITICAL_SECTION_UNLOCK(pmb_obj->lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mbc_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t **param_info)
|
||||
{
|
||||
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->get_cid_info && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly configured.");
|
||||
error = mbm_controller->get_cid_info(ctx, cid, param_info);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master get cid info failure, error=(0x%x).", (uint16_t)error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameter value for characteristic selected by name and cid
|
||||
*/
|
||||
esp_err_t mbc_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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_parameter && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
error = mbm_controller->set_parameter(ctx, cid, value, type);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master set parameter failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameter value for characteristic selected by name and cid
|
||||
*/
|
||||
esp_err_t mbc_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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_parameter_with && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
error = mbm_controller->set_parameter_with(ctx, cid, uid, value, type);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master set parameter failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter data for corresponding characteristic
|
||||
*/
|
||||
esp_err_t mbc_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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->get_parameter && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly configured.");
|
||||
error = mbm_controller->get_parameter(ctx, cid, value, type);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master get parameter failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter data for corresponding characteristic
|
||||
*/
|
||||
esp_err_t mbc_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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->get_parameter_with && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly configured.");
|
||||
error = mbm_controller->get_parameter_with(ctx, cid, uid, value, type);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master get parameter failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send custom Modbus request defined as mb_param_request_t structure
|
||||
*/
|
||||
esp_err_t mbc_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
|
||||
{
|
||||
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->send_request && mbm_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly configured.");
|
||||
error = mbm_controller->send_request(ctx, request, data_ptr);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master send request failure error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Modbus parameter description table
|
||||
*/
|
||||
esp_err_t mbc_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor,
|
||||
const uint16_t num_elements)
|
||||
{
|
||||
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).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus controller stack start function
|
||||
*/
|
||||
esp_err_t mbc_master_start(void *ctx)
|
||||
{
|
||||
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->start, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
error = mbm_controller->start(ctx);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master start failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus controller stack stop function
|
||||
*/
|
||||
esp_err_t mbc_master_stop(void *ctx)
|
||||
{
|
||||
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->stop, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Master interface is not correctly initialized.");
|
||||
error = mbm_controller->stop(ctx);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
|
||||
"Master stop failure, error=(0x%x) (%s).",
|
||||
(uint16_t)error, esp_err_to_name(error));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ----------------------- Callback functions for Modbus stack ---------------------------------*/
|
||||
// These are executed by modbus stack to read appropriate type of registers.
|
||||
|
||||
/**
|
||||
* Modbus master input register callback function.
|
||||
*
|
||||
* @param ctx interface context pointer
|
||||
* @param reg_buffer input register buffer
|
||||
* @param reg_addr input register address
|
||||
* @param num_regs input register number
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
// Callback function for reading of MB Input Registers
|
||||
// mbm_reg_input_cb_serial
|
||||
mb_err_enum_t mbc_reg_input_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr, uint16_t num_regs)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG,
|
||||
"Master stack processing error.");
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
|
||||
// Number of input registers to be transferred
|
||||
uint16_t num_input_regs = (uint16_t)mbm_opts->reg_buffer_size;
|
||||
uint8_t *input_reg_buf = (uint8_t *)mbm_opts->reg_buffer_ptr; // Get instance address
|
||||
uint16_t regs_cnt = num_regs;
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
// If input or configuration parameters are incorrect then return an error to stack layer
|
||||
if ((input_reg_buf) && (num_regs >= 1) && (num_input_regs == regs_cnt))
|
||||
{
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs_cnt > 0)
|
||||
{
|
||||
_XFER_2_RD(input_reg_buf, reg_buffer);
|
||||
regs_cnt -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus master holding register callback function.
|
||||
*
|
||||
* @param ctx interface context pointer
|
||||
* @param reg_buffer holding register buffer
|
||||
* @param reg_addr holding register address
|
||||
* @param num_regs holding register number
|
||||
* @param mode read or write
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
// Callback function for reading of MB Holding Registers
|
||||
// Executed by stack when request to read/write holding registers is received
|
||||
// mbm_reg_holding_cb_serial
|
||||
mb_err_enum_t mbc_reg_holding_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
|
||||
uint16_t num_regs, mb_reg_mode_enum_t mode)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
|
||||
uint16_t num_hold_regs = (uint16_t)mbm_opts->reg_buffer_size;
|
||||
uint8_t *holding_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t regs_cnt = num_regs;
|
||||
// Check input and configuration parameters for correctness
|
||||
if ((holding_buf) && (num_hold_regs == num_regs) && (num_regs >= 1))
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case MB_REG_WRITE:
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs_cnt > 0)
|
||||
{
|
||||
_XFER_2_RD(reg_buffer, holding_buf);
|
||||
regs_cnt -= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MB_REG_READ:
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs_cnt > 0)
|
||||
{
|
||||
_XFER_2_WR(holding_buf, reg_buffer);
|
||||
holding_buf += 2;
|
||||
regs_cnt -= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus master coils callback function.
|
||||
*
|
||||
* @param ctx interface context pointer
|
||||
* @param reg_buffer coils buffer
|
||||
* @param reg_addr coils address
|
||||
* @param ncoils coils number
|
||||
* @param mode read or write
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
// Callback function for reading of MB Coils Registers
|
||||
// mbm_reg_coils_cb_serial
|
||||
mb_err_enum_t mbc_reg_coils_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
|
||||
uint16_t ncoils, mb_reg_mode_enum_t mode)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
|
||||
uint16_t num_coil_regs = (uint16_t)mbm_opts->reg_buffer_size;
|
||||
uint8_t *coils_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t reg_index;
|
||||
uint16_t coils_cnt = ncoils;
|
||||
reg_addr--; // The address is already + 1
|
||||
if ((num_coil_regs >= 1) && (coils_buf) && (ncoils == num_coil_regs))
|
||||
{
|
||||
reg_index = (reg_addr % 8);
|
||||
switch (mode)
|
||||
{
|
||||
case MB_REG_WRITE:
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (coils_cnt > 0)
|
||||
{
|
||||
uint8_t result = mb_util_get_bits((uint8_t *)coils_buf, reg_index - (reg_addr % 8), 1);
|
||||
mb_util_set_bits(reg_buffer, reg_index - (reg_addr % 8), 1, result);
|
||||
reg_index++;
|
||||
coils_cnt--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MB_REG_READ:
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (coils_cnt > 0)
|
||||
{
|
||||
uint8_t result = mb_util_get_bits(reg_buffer, reg_index - (reg_addr % 8), 1);
|
||||
mb_util_set_bits((uint8_t *)coils_buf, reg_index - (reg_addr % 8), 1, result);
|
||||
reg_index++;
|
||||
coils_cnt--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} // switch ( mode )
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the configuration or input parameters are incorrect then return error to stack
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus master discrete callback function.
|
||||
*
|
||||
* @param ctx - pointer to interface structure
|
||||
* @param reg_buffer discrete buffer
|
||||
* @param reg_addr discrete address
|
||||
* @param n_discrete discrete number
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
// Callback function for reading of MB Discrete Input Registers
|
||||
// mbm_reg_discrete_cb_serial
|
||||
mb_err_enum_t mbc_reg_discrete_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
|
||||
uint16_t n_discrete)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
|
||||
uint16_t num_discr_regs = (uint16_t)mbm_opts->reg_buffer_size;
|
||||
uint8_t *discr_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t bit_index, num_reg;
|
||||
uint8_t *temp_discr_buf;
|
||||
num_reg = n_discrete;
|
||||
temp_discr_buf = (uint8_t *)discr_buf;
|
||||
// It is already plus one in Modbus function method.
|
||||
reg_addr--;
|
||||
if ((num_discr_regs >= 1) && (discr_buf) && (n_discrete >= 1) && (n_discrete == num_discr_regs))
|
||||
{
|
||||
bit_index = (uint16_t)(reg_addr) % 8; // Get bit index
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (num_reg > 0)
|
||||
{
|
||||
uint8_t result = mb_util_get_bits(reg_buffer, bit_index - (reg_addr % 8), 1);
|
||||
mb_util_set_bits(temp_discr_buf, bit_index - (reg_addr % 8), 1, result);
|
||||
bit_index++;
|
||||
num_reg--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Helper function to set parameter buffer according to its type
|
||||
esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param_type, size_t param_size)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
MB_RETURN_ON_FALSE((src), ESP_ERR_INVALID_STATE, TAG,"incorrect data pointer.");
|
||||
MB_RETURN_ON_FALSE((dest), ESP_ERR_INVALID_STATE, TAG,"incorrect data pointer.");
|
||||
void *pdest = dest;
|
||||
void *psrc = src;
|
||||
|
||||
// Transfer parameter data into value of characteristic
|
||||
switch(param_type)
|
||||
{
|
||||
case PARAM_TYPE_U8:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8) {
|
||||
*((uint8_t *)pdest) = *((uint8_t*)psrc);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U16:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
|
||||
*((uint16_t *)pdest) = *((uint16_t*)psrc);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U32:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
|
||||
*((uint32_t *)pdest) = *((uint32_t*)psrc);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_FLOAT:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
|
||||
*((float *)pdest) = *(float*)psrc;
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_ASCII:
|
||||
case PARAM_TYPE_BIN:
|
||||
memcpy((void *)dest, (void*)src, (size_t)param_size);
|
||||
break;
|
||||
|
||||
#if CONFIG_FMB_EXT_TYPE_SUPPORT
|
||||
|
||||
case PARAM_TYPE_I8_A:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
|
||||
mb_set_int8_a((val_16_arr *)pdest, (*(int8_t*)psrc));
|
||||
ESP_LOGV(TAG, "Convert uint8 B[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I8_B:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
|
||||
mb_set_int8_b((val_16_arr *)pdest, (int8_t)((*(uint16_t*)psrc) >> 8));
|
||||
ESP_LOGV(TAG, "Convert int8 A[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U8_A:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
|
||||
mb_set_uint8_a((val_16_arr *)pdest, (*(uint8_t*)psrc));
|
||||
ESP_LOGV(TAG, "Convert uint8 A[%d] 0x%02" PRIx16 " = %02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U8_B:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
|
||||
uint8_t data = (uint8_t)((*(uint16_t*)psrc) >> 8);
|
||||
mb_set_uint8_b((val_16_arr *)pdest, data);
|
||||
ESP_LOGV(TAG, "Convert uint8 B[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I16_AB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I16) {
|
||||
mb_set_int16_ab((val_16_arr *)pdest, *(int16_t*)psrc);
|
||||
ESP_LOGV(TAG, "Convert int16 AB[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I16_BA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I16) {
|
||||
mb_set_int16_ba((val_16_arr *)pdest, *(int16_t*)psrc);
|
||||
ESP_LOGV(TAG, "Convert int16 BA[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U16_AB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
|
||||
mb_set_uint16_ab((val_16_arr *)pdest, *(uint16_t*)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint16 AB[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U16_BA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
|
||||
mb_set_uint16_ba((val_16_arr *)pdest, *(uint16_t*)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint16 BA[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I32_ABCD:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
|
||||
mb_set_int32_abcd((val_32_arr *)pdest, *(int32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int32 ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U32_ABCD:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
|
||||
mb_set_uint32_abcd((val_32_arr *)pdest, *(uint32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint32 ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_FLOAT_ABCD:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
|
||||
mb_set_float_abcd((val_32_arr *)pdest, *(float *)psrc);
|
||||
ESP_LOGV(TAG, "Convert float ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I32_CDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
|
||||
mb_set_int32_cdab((val_32_arr *)pdest, *(int32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int32 CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U32_CDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
|
||||
mb_set_uint32_cdab((val_32_arr *)pdest, *(uint32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint32 CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_FLOAT_CDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
|
||||
mb_set_float_cdab((val_32_arr *)pdest, *(float *)psrc);
|
||||
ESP_LOGV(TAG, "Convert float CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I32_BADC:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
|
||||
mb_set_int32_badc((val_32_arr *)pdest, *(int32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int32 BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U32_BADC:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
|
||||
mb_set_uint32_badc((val_32_arr *)pdest, *(uint32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint32 BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_FLOAT_BADC:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
|
||||
mb_set_float_badc((val_32_arr *)pdest, *(float *)psrc);
|
||||
ESP_LOGV(TAG, "Convert float BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I32_DCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
|
||||
mb_set_int32_dcba((val_32_arr *)pdest, *(int32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int32 DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U32_DCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
|
||||
mb_set_uint32_dcba((val_32_arr *)pdest, *(uint32_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint32 DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_FLOAT_DCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
|
||||
mb_set_float_dcba((val_32_arr *)pdest, *(float *)psrc);
|
||||
ESP_LOGV(TAG, "Convert float DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I64_ABCDEFGH:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
|
||||
mb_set_int64_abcdefgh((val_64_arr *)pdest, *(int64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int64 ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U64_ABCDEFGH:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
|
||||
mb_set_uint64_abcdefgh((val_64_arr *)pdest, *(uint64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_DOUBLE_ABCDEFGH:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
|
||||
mb_set_double_abcdefgh((val_64_arr *)pdest, *(double *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I64_HGFEDCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
|
||||
mb_set_int64_hgfedcba((val_64_arr *)pdest, *(int64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int64 HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U64_HGFEDCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
|
||||
mb_set_uint64_hgfedcba((val_64_arr *)pdest, *(uint64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_DOUBLE_HGFEDCBA:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
|
||||
mb_set_double_hgfedcba((val_64_arr *)pdest, *(double *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I64_GHEFCDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
|
||||
mb_set_int64_ghefcdab((val_64_arr *)pdest, *(int64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int64 GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U64_GHEFCDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
|
||||
mb_set_uint64_ghefcdab((val_64_arr *)pdest, *(uint64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint64 GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_DOUBLE_GHEFCDAB:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
|
||||
mb_set_double_ghefcdab((val_64_arr *)pdest, *(double *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_I64_BADCFEHG:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
|
||||
mb_set_int64_badcfehg((val_64_arr *)pdest, *(int64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert int64 BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_U64_BADCFEHG:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
|
||||
mb_set_uint64_badcfehg((val_64_arr *)pdest, *(uint64_t *)psrc);
|
||||
ESP_LOGV(TAG, "Convert uint64 BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_DOUBLE_BADCFEHG:
|
||||
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
|
||||
mb_set_double_badcfehg((val_64_arr *)pdest, *(double *)psrc);
|
||||
ESP_LOGV(TAG, "Convert double BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
|
||||
}
|
||||
break;
|
||||
|
||||
#endif
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect param type (%u).",
|
||||
__FUNCTION__, (unsigned)param_type);
|
||||
err = ESP_ERR_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
38
modbus/mb_controller/common/esp_modbus_master_serial.c
Normal file
38
modbus/mb_controller/common/esp_modbus_master_serial.c
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "mbc_master.h" // for master interface define
|
||||
#include "esp_modbus_master.h" // for public slave defines
|
||||
#include "mbc_serial_master.h" // for public interface defines
|
||||
|
||||
#include "sdkconfig.h" // for KConfig defines
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
/**
|
||||
* Initialization of Modbus master serial
|
||||
*/
|
||||
esp_err_t mbc_master_create_serial(mb_communication_info_t *config, void **handler)
|
||||
{
|
||||
void *ctx = NULL;
|
||||
esp_err_t error = ESP_ERR_NOT_SUPPORTED;
|
||||
switch(config->mode) {
|
||||
case MB_RTU:
|
||||
case MB_ASCII:
|
||||
error = mbc_serial_master_create(config, &ctx);
|
||||
break;
|
||||
default:
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
if ((ctx) && (error == ESP_OK)) {
|
||||
//mbc_master_init_iface(ctx);
|
||||
*handler = ctx;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif
|
28
modbus/mb_controller/common/esp_modbus_master_tcp.c
Normal file
28
modbus/mb_controller/common/esp_modbus_master_tcp.c
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "esp_modbus_master.h" // for public interface defines
|
||||
#include "mbc_tcp_master.h" // for public interface defines
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
/**
|
||||
* Initialization of Modbus TCP Master controller interface
|
||||
*/
|
||||
esp_err_t mbc_master_create_tcp(mb_communication_info_t *config, void **handler)
|
||||
{
|
||||
void *ctx = NULL;
|
||||
esp_err_t error = mbc_tcp_master_create(config, &ctx);
|
||||
|
||||
if ((ctx) && (error == ESP_OK)) {
|
||||
*handler = ctx;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif
|
517
modbus/mb_controller/common/esp_modbus_slave.c
Normal file
517
modbus/mb_controller/common/esp_modbus_slave.c
Normal file
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "esp_timer.h" // for esp_timer_get_time()
|
||||
#include "sdkconfig.h" // for KConfig defines
|
||||
|
||||
#include "mbc_slave.h" // for slave private type definitions
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "esp_modbus_slave.h" // for public slave defines
|
||||
|
||||
#include "mb_utils.h" // for stack bit setting utilities
|
||||
|
||||
#ifdef CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
|
||||
#define MB_ID_BYTE0(id) ((uint8_t)(id))
|
||||
#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
|
||||
#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
|
||||
#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
|
||||
|
||||
#define MB_CONTROLLER_SLAVE_ID (CONFIG_FMB_CONTROLLER_SLAVE_ID)
|
||||
#define MB_SLAVE_ID_SHORT (MB_ID_BYTE3(MB_CONTROLLER_SLAVE_ID))
|
||||
|
||||
// Slave ID constant
|
||||
static uint8_t mb_slave_id[] = { MB_ID_BYTE0(MB_CONTROLLER_SLAVE_ID),
|
||||
MB_ID_BYTE1(MB_CONTROLLER_SLAVE_ID),
|
||||
MB_ID_BYTE2(MB_CONTROLLER_SLAVE_ID) };
|
||||
|
||||
#endif
|
||||
|
||||
static const char TAG[] __attribute__((unused)) = "MB_CONTROLLER_SLAVE";
|
||||
|
||||
// Searches the register in the area specified by type, returns descriptor if found, else NULL
|
||||
static mb_descr_entry_t *mbc_slave_find_reg_descriptor(void *ctx, mb_param_type_t type, uint16_t addr, size_t regs)
|
||||
{
|
||||
mb_descr_entry_t *it;
|
||||
uint16_t reg_size = 0;
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
|
||||
if (LIST_EMPTY(&mbs_opts->area_descriptors[type])) {
|
||||
return NULL;
|
||||
}
|
||||
// search for the register in each area
|
||||
for (it = LIST_FIRST(&mbs_opts->area_descriptors[type]); it != NULL; it = LIST_NEXT(it, entries)) {
|
||||
reg_size = REG_SIZE(type, it->size);
|
||||
if ((addr >= it->start_offset)
|
||||
&& (it->p_data)
|
||||
&& (regs >= 1)
|
||||
&& ((addr + regs) <= (it->start_offset + reg_size))
|
||||
&& (reg_size >= 1)) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mbc_slave_free_descriptors(void *ctx)
|
||||
{
|
||||
mb_descr_entry_t *it;
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
|
||||
for (int descr_type = 0; descr_type < MB_PARAM_COUNT; descr_type++) {
|
||||
while ((it = LIST_FIRST(&mbs_opts->area_descriptors[descr_type]))) {
|
||||
LIST_REMOVE(it, entries);
|
||||
free(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mbc_slave_init_iface(void *ctx)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
|
||||
// Initialize list head for register areas
|
||||
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_INPUT]);
|
||||
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_HOLDING]);
|
||||
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_COIL]);
|
||||
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_DISCRETE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus controller delete function
|
||||
*/
|
||||
esp_err_t mbc_slave_delete(void *ctx)
|
||||
{
|
||||
esp_err_t error = ESP_OK;
|
||||
// Is initialization done?
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
|
||||
// Check if interface has been initialized
|
||||
MB_RETURN_ON_FALSE(mbs_controller->delete,
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly configured.");
|
||||
// Call the slave controller destroy function
|
||||
error = mbs_controller->delete(ctx);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Slave delete failure error=(0x%x).", (uint16_t)error);
|
||||
}
|
||||
// Destroy all opened descriptors
|
||||
mbc_slave_free_descriptors(ctx);
|
||||
free(mbs_controller);
|
||||
mbs_controller = NULL;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical section lock function
|
||||
*/
|
||||
esp_err_t mbc_slave_lock(void *ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_base_t *pmb_obj = (mb_base_t *)mbs_controller->mb_base;
|
||||
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
CRITICAL_SECTION_LOCK(pmb_obj->lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical section unlock function
|
||||
*/
|
||||
esp_err_t mbc_slave_unlock(void *ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_base_t *pmb_obj = (mb_base_t *)mbs_controller->mb_base;
|
||||
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
CRITICAL_SECTION_UNLOCK(pmb_obj->lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Modbus controller start function
|
||||
*/
|
||||
esp_err_t mbc_slave_start(void *ctx)
|
||||
{
|
||||
esp_err_t error = ESP_OK;
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE(mbs_controller->start, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly configured.");
|
||||
#ifdef CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
// Set the slave ID if the KConfig option is selected
|
||||
mb_err_enum_t status = mb_set_slv_id(mbs_controller->mb_base, MB_SLAVE_ID_SHORT, true, (uint8_t *)mb_slave_id, sizeof(mb_slave_id));
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG, "mb stack set slave ID failure.");
|
||||
#endif
|
||||
error = mbs_controller->start(ctx);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave start failure error=(0x%x).", (uint16_t)error);
|
||||
mbs_controller->is_active = true;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Modbus controller stop function
|
||||
*/
|
||||
esp_err_t mbc_slave_stop(void *ctx)
|
||||
{
|
||||
esp_err_t error = ESP_OK;
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE(mbs_controller->stop,
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly configured.");
|
||||
error = mbs_controller->stop(ctx);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave stop failure error=(0x%x).", (uint16_t)error);
|
||||
mbs_controller->is_active = false;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking function to get event on parameter group change for application task
|
||||
*/
|
||||
mb_event_group_t mbc_slave_check_event(void *ctx, mb_event_group_t group)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, MB_EVENT_NO_EVENTS, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE((mbs_controller->check_event && mbs_controller->is_active),
|
||||
MB_EVENT_NO_EVENTS, TAG,
|
||||
"Slave interface is not correctly configured.");
|
||||
mb_event_group_t event = mbs_controller->check_event(ctx, group);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get notification about parameter change from application task
|
||||
*/
|
||||
esp_err_t mbc_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE((mbs_controller->get_param_info && mbs_controller->is_active),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly configured.");
|
||||
return mbs_controller->get_param_info(ctx, reg_info, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set area descriptors for modbus parameters
|
||||
*/
|
||||
esp_err_t mbc_slave_set_descriptor(void *ctx, mb_register_area_descriptor_t descr_data)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
esp_err_t error = ESP_OK;
|
||||
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
|
||||
if (mbs_controller->set_descriptor) {
|
||||
error = mbs_controller->set_descriptor(ctx, descr_data);
|
||||
MB_RETURN_ON_FALSE((error == ESP_OK),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave set descriptor failure error=(0x%x).",
|
||||
(uint16_t)error);
|
||||
} else {
|
||||
mb_slave_options_t *mbs_opts = &mbs_controller->opts;
|
||||
|
||||
MB_RETURN_ON_FALSE((descr_data.size < MB_INST_MAX_SIZE) && (descr_data.size >= MB_INST_MIN_SIZE),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb area size is incorrect.");
|
||||
uint16_t reg_size = REG_SIZE(descr_data.type, descr_data.size);
|
||||
|
||||
// Check if the address is already in the descriptor list
|
||||
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, descr_data.type, descr_data.start_offset, reg_size);
|
||||
if (!it) {
|
||||
// Start register exists in any area?
|
||||
it = mbc_slave_find_reg_descriptor(ctx, descr_data.type, descr_data.start_offset, 1);
|
||||
}
|
||||
|
||||
MB_RETURN_ON_FALSE((it == NULL), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor or already defined.");
|
||||
|
||||
mb_descr_entry_t *new_descr = (mb_descr_entry_t*) heap_caps_malloc(sizeof(mb_descr_entry_t),
|
||||
MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
|
||||
MB_RETURN_ON_FALSE(new_descr, ESP_ERR_NO_MEM, TAG, "mb can not allocate memory for descriptor.");
|
||||
new_descr->start_offset = descr_data.start_offset;
|
||||
new_descr->type = descr_data.type;
|
||||
new_descr->p_data = descr_data.address;
|
||||
new_descr->size = descr_data.size;
|
||||
new_descr->access = descr_data.access;
|
||||
LIST_INSERT_HEAD(&mbs_opts->area_descriptors[descr_data.type], new_descr, entries);
|
||||
error = ESP_OK;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// The helper function to get time stamp in microseconds
|
||||
static uint64_t mbc_slave_get_time_stamp(void)
|
||||
{
|
||||
uint64_t time_stamp = esp_timer_get_time();
|
||||
return time_stamp;
|
||||
}
|
||||
|
||||
// Helper function to send parameter information to application task
|
||||
static esp_err_t mbc_slave_send_param_info(void *ctx, mb_event_group_t par_type, uint16_t mb_offset,
|
||||
uint8_t *par_address, uint16_t par_size)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
esp_err_t error = ESP_FAIL;
|
||||
mb_param_info_t par_info;
|
||||
// Check if queue is not full the send parameter information
|
||||
par_info.type = par_type;
|
||||
par_info.size = par_size;
|
||||
par_info.address = par_address;
|
||||
par_info.time_stamp = mbc_slave_get_time_stamp();
|
||||
par_info.mb_offset = mb_offset;
|
||||
BaseType_t status = xQueueSend(mbs_opts->notification_queue_handle, &par_info, MB_PAR_INFO_TOUT);
|
||||
if (pdTRUE == status) {
|
||||
ESP_LOGD(TAG, "Queue send parameter info (type, address, size): %d, 0x%" PRIx32 ", %d",
|
||||
(int)par_type, (uint32_t)par_address, (int)par_size);
|
||||
error = ESP_OK;
|
||||
} else if (errQUEUE_FULL == status) {
|
||||
ESP_LOGD(TAG, "Parameter queue is overflowed.");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Helper function to send notification
|
||||
static esp_err_t mbc_slave_send_param_access_notification(void *ctx, mb_event_group_t event)
|
||||
{
|
||||
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
|
||||
"Slave interface is not correctly initialized.");
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
esp_err_t err = ESP_FAIL;
|
||||
mb_event_group_t bits = (mb_event_group_t)xEventGroupSetBits(mbs_opts->event_group_handle, (EventBits_t)event);
|
||||
if (bits & event) {
|
||||
ESP_LOGD(TAG, "The MB_REG_CHANGE_EVENT = 0x%.2x is set.", (int)event);
|
||||
err = ESP_OK;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Below are the common slave read/write register callback functions
|
||||
* The concrete slave port can override them using interface function pointers
|
||||
*/
|
||||
|
||||
// Callback function for reading of MB Input Registers
|
||||
mb_err_enum_t mbc_reg_input_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs)
|
||||
{
|
||||
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
|
||||
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
address--; // address of register is already +1
|
||||
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_INPUT, address, n_regs);
|
||||
if (it) {
|
||||
uint16_t input_reg_start = (uint16_t)it->start_offset; // Get Modbus start address
|
||||
uint8_t *input_buffer = (uint8_t *)it->p_data; // Get instance address
|
||||
uint16_t regs = n_regs;
|
||||
uint16_t reg_index;
|
||||
// If input or configuration parameters are incorrect then return an error to stack layer
|
||||
reg_index = (uint16_t)(address - input_reg_start);
|
||||
reg_index <<= 1; // register Address to byte address
|
||||
input_buffer += reg_index;
|
||||
uint8_t *buffer_start = input_buffer;
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs > 0) {
|
||||
_XFER_2_RD(reg_buffer, input_buffer);
|
||||
reg_index += 2;
|
||||
regs -= 1;
|
||||
}
|
||||
}
|
||||
// Send access notification
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_INPUT_REG_RD);
|
||||
// Send parameter info to application task
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_INPUT_REG_RD, address,
|
||||
(uint8_t *)buffer_start, n_regs);
|
||||
} else {
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Callback function for reading of MB Holding Registers
|
||||
// Executed by stack when request to read/write holding registers is received
|
||||
mb_err_enum_t mbc_reg_holding_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode)
|
||||
{
|
||||
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
|
||||
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t reg_index;
|
||||
address--; // address of register is already +1
|
||||
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_HOLDING, address, n_regs);
|
||||
if (it) {
|
||||
uint16_t reg_holding_start = (uint16_t)it->start_offset; // Get Modbus start address
|
||||
uint8_t *holding_buffer = (uint8_t *)it->p_data; // Get instance address
|
||||
uint16_t regs = n_regs;
|
||||
reg_index = (uint16_t) (address - reg_holding_start);
|
||||
reg_index <<= 1; // register Address to byte address
|
||||
holding_buffer += reg_index;
|
||||
uint8_t *buffer_start = holding_buffer;
|
||||
switch (mode) {
|
||||
case MB_REG_READ:
|
||||
if (it->access != MB_ACCESS_WO) {
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs > 0) {
|
||||
_XFER_2_RD(reg_buffer, holding_buffer);
|
||||
reg_index += 2;
|
||||
regs -= 1;
|
||||
};
|
||||
}
|
||||
// Send access notification
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_HOLDING_REG_RD);
|
||||
// Send parameter info
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_HOLDING_REG_RD, address,
|
||||
(uint8_t *)buffer_start, n_regs);
|
||||
} else {
|
||||
status = MB_EINVAL;
|
||||
}
|
||||
break;
|
||||
case MB_REG_WRITE:
|
||||
if (it->access != MB_ACCESS_RO) {
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (regs > 0) {
|
||||
_XFER_2_WR(holding_buffer, reg_buffer);
|
||||
holding_buffer += 2;
|
||||
reg_index += 2;
|
||||
regs -= 1;
|
||||
};
|
||||
}
|
||||
// Send access notification
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_HOLDING_REG_WR);
|
||||
// Send parameter info
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_HOLDING_REG_WR, (uint16_t)address,
|
||||
(uint8_t *)buffer_start, (uint16_t)n_regs);
|
||||
} else {
|
||||
status = MB_EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Callback function for reading of MB Coils Registers
|
||||
mb_err_enum_t mbc_reg_coils_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode)
|
||||
{
|
||||
void *ctx =(void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
|
||||
MB_RETURN_ON_FALSE(ctx, MB_EILLSTATE, TAG, "Slave stack uninitialized.");
|
||||
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t reg_index;
|
||||
uint16_t coils = n_coils;
|
||||
address--; // The address is already +1
|
||||
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_COIL, address, n_coils);
|
||||
if (it) {
|
||||
uint16_t reg_coils_start = (uint16_t)it->start_offset; // MB offset of coils
|
||||
uint8_t *reg_coils_buf = (uint8_t *)it->p_data;
|
||||
reg_index = (uint16_t) (address - it->start_offset);
|
||||
char *coils_data_buf = (char *)(reg_coils_buf + (reg_index >> 3));
|
||||
switch (mode) {
|
||||
case MB_REG_READ:
|
||||
if (it->access != MB_ACCESS_WO) {
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (coils > 0) {
|
||||
uint8_t result = mb_util_get_bits((uint8_t *)reg_coils_buf, reg_index, 1);
|
||||
mb_util_set_bits(reg_buffer, reg_index - (address - reg_coils_start), 1, result);
|
||||
reg_index++;
|
||||
coils--;
|
||||
}
|
||||
}
|
||||
// Send an event to notify application task about event
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_COILS_RD);
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_COILS_RD, address,
|
||||
(uint8_t *)(coils_data_buf), n_coils);
|
||||
} else {
|
||||
status = MB_EINVAL;
|
||||
}
|
||||
break;
|
||||
case MB_REG_WRITE:
|
||||
if (it->access != MB_ACCESS_RO) {
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (coils > 0) {
|
||||
uint8_t result = mb_util_get_bits(reg_buffer,
|
||||
reg_index - (address - reg_coils_start), 1);
|
||||
mb_util_set_bits((uint8_t *)reg_coils_buf, reg_index, 1, result);
|
||||
reg_index++;
|
||||
coils--;
|
||||
}
|
||||
}
|
||||
// Send an event to notify application task about event
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_COILS_WR);
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_COILS_WR, address,
|
||||
(uint8_t *)coils_data_buf, n_coils);
|
||||
} else {
|
||||
status = MB_EINVAL;
|
||||
}
|
||||
break;
|
||||
} // switch ( mode )
|
||||
} else {
|
||||
// If the configuration or input parameters are incorrect then return error to stack
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Callback function for reading of MB Discrete Input Registers
|
||||
mb_err_enum_t mbc_reg_discrete_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete)
|
||||
{
|
||||
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
|
||||
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t reg_index;
|
||||
uint16_t reg_bit_index;
|
||||
uint16_t n_reg;
|
||||
uint8_t *discrete_input_buf;
|
||||
// It already plus one in modbus function method.
|
||||
address--;
|
||||
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_DISCRETE, address, n_discrete);
|
||||
if (it) {
|
||||
uint16_t reg_discrete_start = (uint16_t)it->start_offset; // MB offset of registers
|
||||
n_reg = (n_discrete >> 3) + 1;
|
||||
discrete_input_buf = (uint8_t *)it->p_data; // the storage address
|
||||
reg_index = (uint16_t) (address - reg_discrete_start) / 8; // Get register index in the buffer for bit number
|
||||
reg_bit_index = (uint16_t)(address - reg_discrete_start) % 8; // Get bit index
|
||||
uint8_t *temp_buf = &discrete_input_buf[reg_index];
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
while (n_reg > 0) {
|
||||
*reg_buffer++ = mb_util_get_bits(&discrete_input_buf[reg_index++], reg_bit_index, 8);
|
||||
n_reg--;
|
||||
}
|
||||
}
|
||||
reg_buffer--;
|
||||
// Last discrete
|
||||
n_discrete = n_discrete % 8;
|
||||
// Filling zero to high bit
|
||||
*reg_buffer = *reg_buffer << (8 - n_discrete);
|
||||
*reg_buffer = *reg_buffer >> (8 - n_discrete);
|
||||
// Send an event to notify application task about event
|
||||
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_DISCRETE_RD);
|
||||
(void)mbc_slave_send_param_info(ctx, MB_EVENT_DISCRETE_RD, address,
|
||||
(uint8_t *)temp_buf, n_discrete);
|
||||
} else {
|
||||
status = MB_ENOREG;
|
||||
}
|
||||
return status;
|
||||
}
|
41
modbus/mb_controller/common/esp_modbus_slave_serial.c
Normal file
41
modbus/mb_controller/common/esp_modbus_slave_serial.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "sdkconfig.h" // for KConfig defines
|
||||
#include "mbc_slave.h" // for slave interface define
|
||||
#include "esp_modbus_slave.h" // for public slave defines
|
||||
#include "mbc_serial_slave.h" // for public interface defines
|
||||
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
/**
|
||||
* Initialization of Modbus Serial slave controller
|
||||
*/
|
||||
esp_err_t mbc_slave_create_serial(mb_communication_info_t *config, void **handler)
|
||||
{
|
||||
void *ctx = NULL;
|
||||
esp_err_t error = ESP_ERR_NOT_SUPPORTED;
|
||||
switch(config->mode)
|
||||
{
|
||||
case MB_RTU:
|
||||
case MB_ASCII:
|
||||
// Call constructor function of actual port implementation
|
||||
error = mbc_serial_slave_create(config, &ctx);
|
||||
break;
|
||||
default:
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
if ((ctx) && (error == ESP_OK)) {
|
||||
mbc_slave_init_iface(ctx);
|
||||
*handler = ctx;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif
|
30
modbus/mb_controller/common/esp_modbus_slave_tcp.c
Normal file
30
modbus/mb_controller/common/esp_modbus_slave_tcp.c
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h" // for esp_err_t
|
||||
#include "esp_modbus_slave.h" // for public slave defines
|
||||
#include "mbc_tcp_slave.h" // for public interface defines
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
/**
|
||||
* Initialization of Modbus TCP Slave controller
|
||||
*/
|
||||
esp_err_t mbc_slave_create_tcp(mb_communication_info_t *config, void **handler)
|
||||
{
|
||||
void *ctx = NULL;
|
||||
esp_err_t error = mbc_tcp_slave_create(config, &ctx);
|
||||
|
||||
if ((ctx) && (error == ESP_OK)) {
|
||||
mbc_slave_init_iface(ctx);
|
||||
*handler = ctx;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif
|
171
modbus/mb_controller/common/include/esp_modbus_common.h
Normal file
171
modbus/mb_controller/common/include/esp_modbus_common.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/uart.h" // for UART types
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_FMB_EXT_TYPE_SUPPORT
|
||||
#include "mb_endianness_utils.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "port_common.h"
|
||||
|
||||
#if __has_include("esp_check.h")
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) ESP_RETURN_ON_FALSE(a, err_code, tag, format __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
#else
|
||||
|
||||
// if cannot include esp_check then use custom check macro
|
||||
|
||||
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) do { \
|
||||
if (!(a)) { \
|
||||
ESP_LOGE(tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
|
||||
return err_code; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif
|
||||
|
||||
#define MB_SLAVE_ADDR_PLACEHOLDER (0xFF)
|
||||
#define MB_CONTROLLER_STACK_SIZE (CONFIG_FMB_CONTROLLER_STACK_SIZE) // Stack size for Modbus controller
|
||||
#define MB_CONTROLLER_PRIORITY (CONFIG_FMB_PORT_TASK_PRIO - 1) // priority of MB controller task
|
||||
#define MB_PORT_TASK_AFFINITY (CONFIG_FMB_PORT_TASK_AFFINITY)
|
||||
|
||||
// Default port defines
|
||||
#define MB_PAR_INFO_TOUT (10) // Timeout for get parameter info
|
||||
#define MB_PARITY_NONE (UART_PARITY_DISABLE)
|
||||
#define MB_SECTION(lock) CRITICAL_SECTION(lock) {}
|
||||
|
||||
// The Macros below handle the endianness while transfer N byte data into buffer
|
||||
#define _XFER_4_RD(dst, src) { \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 1); \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 0); \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 3); \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 2); \
|
||||
(src) += 4; \
|
||||
}
|
||||
|
||||
#define _XFER_2_RD(dst, src) { \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 1); \
|
||||
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 0); \
|
||||
(src) += 2; \
|
||||
}
|
||||
|
||||
#define _XFER_4_WR(dst, src) { \
|
||||
*(uint8_t *)(dst + 1) = *(uint8_t *)(src)++; \
|
||||
*(uint8_t *)(dst + 0) = *(uint8_t *)(src)++; \
|
||||
*(uint8_t *)(dst + 3) = *(uint8_t *)(src)++; \
|
||||
*(uint8_t *)(dst + 2) = *(uint8_t *)(src)++ ; \
|
||||
}
|
||||
|
||||
#define _XFER_2_WR(dst, src) { \
|
||||
*(uint8_t *)(dst + 1) = *(uint8_t *)(src)++; \
|
||||
*(uint8_t *)(dst + 0) = *(uint8_t *)(src)++; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Types of actual Modbus implementation
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
MB_PORT_SERIAL_MASTER = 0x00, /*!< Modbus port type serial master. */
|
||||
MB_PORT_SERIAL_SLAVE, /*!< Modbus port type serial slave. */
|
||||
MB_PORT_TCP_MASTER, /*!< Modbus port type TCP master. */
|
||||
MB_PORT_TCP_SLAVE, /*!< Modbus port type TCP slave. */
|
||||
MB_PORT_COUNT, /*!< Modbus port count. */
|
||||
MB_PORT_INACTIVE = 0xFF
|
||||
} mb_port_type_t;
|
||||
|
||||
/**
|
||||
* @brief Event group for parameters notification
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
MB_EVENT_NO_EVENTS = 0x00,
|
||||
MB_EVENT_HOLDING_REG_WR = BIT0, /*!< Modbus Event Write Holding registers. */
|
||||
MB_EVENT_HOLDING_REG_RD = BIT1, /*!< Modbus Event Read Holding registers. */
|
||||
MB_EVENT_INPUT_REG_RD = BIT3, /*!< Modbus Event Read Input registers. */
|
||||
MB_EVENT_COILS_WR = BIT4, /*!< Modbus Event Write Coils. */
|
||||
MB_EVENT_COILS_RD = BIT5, /*!< Modbus Event Read Coils. */
|
||||
MB_EVENT_DISCRETE_RD = BIT6, /*!< Modbus Event Read Discrete bits. */
|
||||
MB_EVENT_STACK_STARTED = BIT7, /*!< Modbus Event Stack started */
|
||||
MB_EVENT_STACK_CONNECTED = BIT8 /*!< Modbus Event Stack started */
|
||||
} mb_event_group_t;
|
||||
|
||||
/**
|
||||
* @brief Type of Modbus parameter
|
||||
*/
|
||||
typedef enum {
|
||||
MB_PARAM_HOLDING = 0x00, /*!< Modbus Holding register. */
|
||||
MB_PARAM_INPUT, /*!< Modbus Input register. */
|
||||
MB_PARAM_COIL, /*!< Modbus Coils. */
|
||||
MB_PARAM_DISCRETE, /*!< Modbus Discrete bits. */
|
||||
MB_PARAM_COUNT,
|
||||
MB_PARAM_UNKNOWN = 0xFF
|
||||
} mb_param_type_t;
|
||||
|
||||
typedef enum _mb_comm_mode mb_mode_type_t;
|
||||
|
||||
typedef struct mb_base_t mb_base_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus TCP type of address for communication.
|
||||
*/
|
||||
typedef enum _addr_type_enum mb_tcp_addr_type_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus TCP communication options structure.
|
||||
*/
|
||||
typedef struct _port_tcp_opts mb_tcp_opts_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus serial communication options structure.
|
||||
*/
|
||||
typedef struct _port_serial_opts mb_serial_opts_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus common communication options structure.
|
||||
*/
|
||||
typedef struct _port_common_opts mb_common_opts_t;
|
||||
|
||||
/**
|
||||
* @brief Device communication structure to setup Modbus controller
|
||||
*/
|
||||
typedef union
|
||||
{
|
||||
mb_comm_mode_t mode; /*!< mode option to check the communication object type*/
|
||||
mb_common_opts_t common_opts; /*!< Common options for communication object. */
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
mb_tcp_opts_t tcp_opts; /*!< tcp options for communication object */
|
||||
#endif
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
mb_serial_opts_t ser_opts; /*!< serial options for communication object */
|
||||
#endif
|
||||
} mb_communication_info_t;
|
||||
|
||||
/**
|
||||
* common interface method types
|
||||
*/
|
||||
typedef esp_err_t (*iface_create_fp)(mb_communication_info_t*, void **); /*!< Interface method create */
|
||||
typedef esp_err_t (*iface_method_default_fp)(void *ctx); /*!< Interface method default prototype */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
472
modbus/mb_controller/common/include/esp_modbus_master.h
Normal file
472
modbus/mb_controller/common/include/esp_modbus_master.h
Normal file
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MB_MASTER_ASSERT(con) do { \
|
||||
if (!(con)) { ESP_LOGE(TAG, "assert errno:%d, errno_str: !(%s)", errno, strerror(errno)); assert(0 && #con); } \
|
||||
} while (0)
|
||||
|
||||
#define MB_MASTER_GET_IFACE(pctx) (__extension__( \
|
||||
{ \
|
||||
MB_MASTER_ASSERT((pctx)); \
|
||||
((mbm_controller_iface_t*)pctx); \
|
||||
} \
|
||||
))
|
||||
|
||||
#define MB_MASTER_GET_OPTS(pctx) (&MB_MASTER_GET_IFACE(pctx)->opts)
|
||||
|
||||
#define MB_MASTER_IS_ACTIVE(pctx) ((bool)(MB_MASTER_GET_IFACE(pctx)->is_active))
|
||||
|
||||
#define MB_MASTER_GET_IFACE_FROM_BASE(pinst) (__extension__( \
|
||||
{ \
|
||||
MB_MASTER_ASSERT(pinst); \
|
||||
mb_base_t *pbase = (mb_base_t *)pinst; \
|
||||
MB_RETURN_ON_FALSE(pbase->descr.parent, MB_EILLSTATE, TAG, "Master interface is not correctly initialized."); \
|
||||
((mbm_controller_iface_t*)pbase->descr.parent); \
|
||||
} \
|
||||
))
|
||||
|
||||
/*!
|
||||
* \brief The macro to access arrays of elements for type conversion.
|
||||
*/
|
||||
#define MB_EACH_ELEM(psrc, pdest, arr_size, elem_size) \
|
||||
(int i = 0; (i < (arr_size / elem_size)); i++, pdest += elem_size, psrc += elem_size)
|
||||
|
||||
/*!
|
||||
* \brief Modbus descriptor table parameter type defines.
|
||||
*/
|
||||
typedef enum {
|
||||
PARAM_TYPE_U8 = 0x00, /*!< Unsigned 8 */
|
||||
PARAM_TYPE_U16 = 0x01, /*!< Unsigned 16 */
|
||||
PARAM_TYPE_U32 = 0x02, /*!< Unsigned 32 */
|
||||
PARAM_TYPE_FLOAT = 0x03, /*!< Float type */
|
||||
PARAM_TYPE_ASCII = 0x04, /*!< ASCII type */
|
||||
PARAM_TYPE_BIN = 0x07, /*!< BIN type */
|
||||
PARAM_TYPE_I8_A = 0x0A, /*!< I8 signed integer in high byte of register */
|
||||
PARAM_TYPE_I8_B = 0x0B, /*!< I8 signed integer in low byte of register */
|
||||
PARAM_TYPE_U8_A = 0x0C, /*!< U8 unsigned integer written to hi byte of register */
|
||||
PARAM_TYPE_U8_B = 0x0D, /*!< U8 unsigned integer written to low byte of register */
|
||||
PARAM_TYPE_I16_AB = 0x0E, /*!< I16 signed integer, big endian */
|
||||
PARAM_TYPE_I16_BA = 0x0F, /*!< I16 signed integer, little endian */
|
||||
PARAM_TYPE_U16_AB = 0x10, /*!< U16 unsigned integer, big endian*/
|
||||
PARAM_TYPE_U16_BA = 0x11, /*!< U16 unsigned integer, little endian */
|
||||
PARAM_TYPE_I32_ABCD = 0x12, /*!< I32 ABCD signed integer, big endian */
|
||||
PARAM_TYPE_I32_CDAB = 0x13, /*!< I32 CDAB signed integer, big endian, reversed register order */
|
||||
PARAM_TYPE_I32_BADC = 0x14, /*!< I32 BADC signed integer, little endian, reversed register order */
|
||||
PARAM_TYPE_I32_DCBA = 0x15, /*!< I32 DCBA signed integer, little endian */
|
||||
PARAM_TYPE_U32_ABCD = 0x16, /*!< U32 ABCD unsigned integer, big endian */
|
||||
PARAM_TYPE_U32_CDAB = 0x17, /*!< U32 CDAB unsigned integer, big endian, reversed register order */
|
||||
PARAM_TYPE_U32_BADC = 0x18, /*!< U32 BADC unsigned integer, little endian, reversed register order */
|
||||
PARAM_TYPE_U32_DCBA = 0x19, /*!< U32 DCBA unsigned integer, little endian */
|
||||
PARAM_TYPE_FLOAT_ABCD = 0x1A, /*!< Float ABCD floating point, big endian */
|
||||
PARAM_TYPE_FLOAT_CDAB = 0x1B, /*!< Float CDAB floating point big endian, reversed register order */
|
||||
PARAM_TYPE_FLOAT_BADC = 0x1C, /*!< Float BADC floating point, little endian, reversed register order */
|
||||
PARAM_TYPE_FLOAT_DCBA = 0x1D, /*!< Float DCBA floating point, little endian */
|
||||
PARAM_TYPE_I64_ABCDEFGH = 0x1E, /*!< I64, ABCDEFGH signed integer, big endian */
|
||||
PARAM_TYPE_I64_HGFEDCBA = 0x1F, /*!< I64, HGFEDCBA signed integer, little endian */
|
||||
PARAM_TYPE_I64_GHEFCDAB = 0x20, /*!< I64, GHEFCDAB signed integer, big endian, reversed register order */
|
||||
PARAM_TYPE_I64_BADCFEHG = 0x21, /*!< I64, BADCFEHG signed integer, little endian, reversed register order */
|
||||
PARAM_TYPE_U64_ABCDEFGH = 0x22, /*!< U64, ABCDEFGH unsigned integer, big endian */
|
||||
PARAM_TYPE_U64_HGFEDCBA = 0x23, /*!< U64, HGFEDCBA unsigned integer, little endian */
|
||||
PARAM_TYPE_U64_GHEFCDAB = 0x24, /*!< U64, GHEFCDAB unsigned integer, big endian, reversed register order */
|
||||
PARAM_TYPE_U64_BADCFEHG = 0x25, /*!< U64, BADCFEHG unsigned integer, little endian, reversed register order */
|
||||
PARAM_TYPE_DOUBLE_ABCDEFGH = 0x26, /*!< Double ABCDEFGH floating point, big endian*/
|
||||
PARAM_TYPE_DOUBLE_HGFEDCBA = 0x27, /*!< Double HGFEDCBA floating point, little endian*/
|
||||
PARAM_TYPE_DOUBLE_GHEFCDAB = 0x28, /*!< Double GHEFCDAB floating point, big endian, reversed register order */
|
||||
PARAM_TYPE_DOUBLE_BADCFEHG = 0x29 /*!< Double BADCFEHG floating point, little endian, reversed register order */
|
||||
} mb_descr_type_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus descriptor table parameter size in bytes.
|
||||
*/
|
||||
typedef enum {
|
||||
PARAM_SIZE_U8 = 0x01, /*!< Unsigned 8 */
|
||||
PARAM_SIZE_U8_REG = 0x02, /*!< Unsigned 8, register value */
|
||||
PARAM_SIZE_I8_REG = 0x02, /*!< Signed 8, register value */
|
||||
PARAM_SIZE_I16 = 0x02, /*!< Unsigned 16 */
|
||||
PARAM_SIZE_U16 = 0x02, /*!< Unsigned 16 */
|
||||
PARAM_SIZE_I32 = 0x04, /*!< Signed 32 */
|
||||
PARAM_SIZE_U32 = 0x04, /*!< Unsigned 32 */
|
||||
PARAM_SIZE_FLOAT = 0x04, /*!< Float 32 size */
|
||||
PARAM_SIZE_ASCII = 0x08, /*!< ASCII size default*/
|
||||
PARAM_SIZE_ASCII24 = 0x18, /*!< ASCII24 size */
|
||||
PARAM_SIZE_I64 = 0x08, /*!< Signed integer 64 size */
|
||||
PARAM_SIZE_U64 = 0x08, /*!< Unsigned integer 64 size */
|
||||
PARAM_SIZE_DOUBLE = 0x08, /*!< Double 64 size */
|
||||
PARAM_MAX_SIZE
|
||||
} mb_descr_size_t;
|
||||
|
||||
/*!
|
||||
* \brief Modbus parameter options for description table (associated with the characteristic).
|
||||
* and can be used in user application to process data.
|
||||
*/
|
||||
typedef union {
|
||||
struct {
|
||||
int opt1; /*!< Parameter option1 */
|
||||
int opt2; /*!< Parameter option2 */
|
||||
int opt3; /*!< Parameter option3 */
|
||||
}; /*!< Parameter options version 1 */
|
||||
struct {
|
||||
int min; /*!< Parameter minimum value */
|
||||
int max; /*!< Parameter maximum value */
|
||||
int step; /*!< Step of parameter change tracking */
|
||||
}; /*!< Parameter options version 2 */
|
||||
} mb_parameter_opt_t;
|
||||
|
||||
/**
|
||||
* @brief Permissions for the characteristics
|
||||
*/
|
||||
typedef enum {
|
||||
PAR_PERMS_READ = 1 << BIT0, /**< the characteristic of the device are readable */
|
||||
PAR_PERMS_WRITE = 1 << BIT1, /**< the characteristic of the device are writable*/
|
||||
PAR_PERMS_TRIGGER = 1 << BIT2, /**< the characteristic of the device are triggerable */
|
||||
PAR_PERMS_READ_WRITE = PAR_PERMS_READ | PAR_PERMS_WRITE, /**< the characteristic of the device are readable & writable */
|
||||
PAR_PERMS_READ_TRIGGER = PAR_PERMS_READ | PAR_PERMS_TRIGGER, /**< the characteristic of the device are readable & triggerable */
|
||||
PAR_PERMS_WRITE_TRIGGER = PAR_PERMS_WRITE | PAR_PERMS_TRIGGER, /**< the characteristic of the device are writable & triggerable */
|
||||
PAR_PERMS_READ_WRITE_TRIGGER = PAR_PERMS_READ_WRITE | PAR_PERMS_TRIGGER, /**< the characteristic of the device are readable & writable & triggerable */
|
||||
} mb_param_perms_t;
|
||||
|
||||
/**
|
||||
* @brief Characteristics descriptor type is used to describe characteristic and
|
||||
* link it with Modbus parameters that reflect its data.
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t cid; /*!< Characteristic cid */
|
||||
const char * param_key; /*!< The key (name) of the parameter */
|
||||
const char * param_units; /*!< The physical units of the parameter */
|
||||
uint8_t mb_slave_addr; /*!< Slave address of device in the Modbus segment */
|
||||
mb_param_type_t mb_param_type; /*!< Type of modbus parameter */
|
||||
uint16_t mb_reg_start; /*!< This is the Modbus register address. This is the 0 based value. */
|
||||
uint16_t mb_size; /*!< Size of mb parameter in registers */
|
||||
uint32_t param_offset; /*!< Parameter name (OFFSET in the parameter structure or address of instance) */
|
||||
mb_descr_type_t param_type; /*!< Float, U8, U16, U32, ASCII, etc. */
|
||||
mb_descr_size_t param_size; /*!< Number of bytes in the parameter. */
|
||||
mb_parameter_opt_t param_opts; /*!< Parameter options used to check limits and etc. */
|
||||
mb_param_perms_t access; /*!< Access permissions based on mode */
|
||||
} mb_parameter_descriptor_t;
|
||||
|
||||
/**
|
||||
* @brief Modbus register request type structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t slave_addr; /*!< Modbus slave address */
|
||||
uint8_t command; /*!< Modbus command to send */
|
||||
uint16_t reg_start; /*!< Modbus start register */
|
||||
uint16_t reg_size; /*!< Modbus number of registers */
|
||||
} mb_param_request_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus controller and stack for TCP port
|
||||
*
|
||||
* @param[out] ctx pointer to master interface structure
|
||||
* @param[in] config - the pointer to stack configuration structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
* - ESP_ERR_NOT_SUPPORTED Port type not supported
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_master_create_tcp(mb_communication_info_t *config, void ** ctx);
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus Master controller and stack for Serial port
|
||||
*
|
||||
* @param[out] ctx pointer to master interface structure
|
||||
* @param[in] config the pointer to configuration structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
* - ESP_ERR_NOT_SUPPORTED Port type not supported
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_master_create_serial(mb_communication_info_t *config, void ** ctx);
|
||||
|
||||
/**
|
||||
* @brief Deletes Modbus controller and stack engine
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Parameter error
|
||||
*/
|
||||
esp_err_t mbc_master_delete(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Critical section lock function for parameter access
|
||||
*
|
||||
* @param[in] ctx pointer to master interface structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_master_lock(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Critical section unlock function for parameter access
|
||||
*
|
||||
* @param[in] ctx pointer to master interface structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_master_unlock(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Starts Modbus communication stack
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus master interface structure
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Modbus stack start error
|
||||
*/
|
||||
esp_err_t mbc_master_start(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Stops Modbus communication stack
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Modbus stack stop error
|
||||
*/
|
||||
esp_err_t mbc_master_stop(void *ctx);
|
||||
|
||||
/***************************** Specific interface functions ********************************************
|
||||
* Interface functions below provide basic methods to read/write access to slave devices in Modbus
|
||||
* segment as well as API to read specific supported characteristics linked to Modbus parameters
|
||||
* of devices in Modbus network.
|
||||
*******************************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Assign parameter description table for Modbus controller interface.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] descriptor pointer to parameter description table
|
||||
* @param num_elements number of elements in the table
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - set descriptor successfully
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument in function call
|
||||
*/
|
||||
esp_err_t mbc_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements);
|
||||
|
||||
/**
|
||||
* @brief Send data request as defined in parameter request, waits response
|
||||
* from slave and returns status of command execution. This function provides standard way
|
||||
* for read/write access to Modbus devices in the network.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] request pointer to request structure of type mb_param_request_t
|
||||
* @param[in] data_ptr pointer to data buffer to send or received data (dependent of command field in request)
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function
|
||||
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
|
||||
* - esp_err_t ESP_ERR_TIMEOUT - operation timeout or no response from slave
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
|
||||
*/
|
||||
esp_err_t mbc_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr);
|
||||
|
||||
/**
|
||||
* @brief Get information about supported characteristic defined as cid. Uses parameter description table to get
|
||||
* this information. The function will check if characteristic defined as a cid parameter is supported
|
||||
* and returns its description in param_info. Returns ESP_ERR_NOT_FOUND if characteristic is not supported.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] cid characteristic id
|
||||
* @param param_info pointer to pointer of characteristic data.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and buffer contains the supported characteristic name
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the characteristic (cid) not found
|
||||
* - esp_err_t ESP_FAIL - unknown error during lookup table processing
|
||||
*/
|
||||
esp_err_t mbc_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t** param_info);
|
||||
|
||||
/**
|
||||
* @brief Read parameter from modbus slave device whose name is defined by name and has cid.
|
||||
* The additional data for request is taken from parameter description (lookup) table.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] cid id of the characteristic for parameter
|
||||
* @param[out] value pointer to data buffer of parameter
|
||||
* @param[out] type parameter type associated with the name returned from parameter description table.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and value buffer contains
|
||||
* representation of actual parameter data from slave
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
|
||||
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
|
||||
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
|
||||
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the parameter is not found in the parameter description table
|
||||
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
|
||||
*/
|
||||
esp_err_t mbc_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type);
|
||||
|
||||
/**
|
||||
* @brief Read parameter from modbus slave device whose name is defined by name and has cid.
|
||||
* The additional data for request is taken from parameter description (lookup) table.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] cid id of the characteristic for parameter
|
||||
* @param[in] uid unit identificator of the slave to set parameter
|
||||
* @param[out] value pointer to data buffer of parameter
|
||||
* @param[out] type parameter type associated with the name returned from parameter description table.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and value buffer contains
|
||||
* representation of actual parameter data from slave
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
|
||||
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
|
||||
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
|
||||
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the parameter is not found in the parameter description table
|
||||
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
|
||||
*/
|
||||
esp_err_t mbc_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type);
|
||||
|
||||
/**
|
||||
* @brief Set characteristic's value defined as a name and cid parameter.
|
||||
* The additional data for cid parameter request is taken from master parameter lookup table.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] cid id of the characteristic for parameter
|
||||
* @param[out] value pointer to data buffer of parameter (actual representation of json value field in binary form)
|
||||
* @param[out] type pointer to parameter type associated with the name returned from parameter lookup table.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
|
||||
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave during processing of parameter
|
||||
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
|
||||
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
|
||||
*/
|
||||
esp_err_t mbc_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type);
|
||||
|
||||
/**
|
||||
* @brief Set characteristic's value defined as a name and cid parameter.
|
||||
* The additional data for cid parameter request is taken from master parameter lookup table.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] cid id of the characteristic for parameter
|
||||
* @param[in] uid unit identificator of the slave to set parameter
|
||||
* @param[out] value pointer to data buffer of parameter (actual representation of json value field in binary form)
|
||||
* @param[out] type pointer to parameter type associated with the name returned from parameter lookup table.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
|
||||
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave during processing of parameter
|
||||
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
|
||||
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
|
||||
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
|
||||
*/
|
||||
esp_err_t mbc_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type);
|
||||
|
||||
/**
|
||||
* @brief Holding register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
|
||||
* @param[in] n_regs - number of registers
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_holding_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Input register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_regs - number of registers
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_input_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Discrete register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_discrete - number of discrete registers
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_discrete_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Coil register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_coils - number of coil registers
|
||||
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_coils_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief The helper function to set data of parameters according to its type
|
||||
*
|
||||
* @param[in] dest the destination address of the parameter
|
||||
* @param[in] src the source address of the parameter
|
||||
* @param[out] param_type type of parameter from data dictionary
|
||||
* @param[out] param_size the storage size of the characteristic (in bytes).
|
||||
* Describes the size of data to keep into data instance during mapping.
|
||||
*
|
||||
* @return
|
||||
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
|
||||
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
|
||||
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
|
||||
*/
|
||||
esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param_type, size_t param_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
264
modbus/mb_controller/common/include/esp_modbus_slave.h
Normal file
264
modbus/mb_controller/common/include/esp_modbus_slave.h
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// 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
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define REG_SIZE(type, nregs) ((type == MB_PARAM_INPUT) || (type == MB_PARAM_HOLDING)) ? (nregs >> 1) : (nregs << 3)
|
||||
|
||||
#define MB_SLAVE_ASSERT(con) do { \
|
||||
if (!(con)) { ESP_LOGE(TAG, "assert errno:%d, errno_str: !(%s)", errno, strerror(errno)); assert(0 && #con); } \
|
||||
} while (0)
|
||||
|
||||
#define MB_SLAVE_GET_IFACE(pctx) (__extension__( \
|
||||
{ \
|
||||
MB_SLAVE_ASSERT((pctx)); \
|
||||
((mbs_controller_iface_t*)pctx); \
|
||||
} \
|
||||
))
|
||||
|
||||
#define MB_SLAVE_GET_OPTS(pctx) (&MB_SLAVE_GET_IFACE(pctx)->opts)
|
||||
|
||||
#define MB_SLAVE_IS_ACTIVE(pctx) ((bool)(MB_SLAVE_GET_IFACE(pctx)->is_active))
|
||||
|
||||
#define MB_SLAVE_GET_IFACE_FROM_BASE(pinst) (__extension__( \
|
||||
{ \
|
||||
MB_SLAVE_ASSERT(pinst); \
|
||||
mb_base_t *pbase = (mb_base_t*)pinst; \
|
||||
MB_RETURN_ON_FALSE(pbase->descr.parent, MB_EILLSTATE, TAG, "Slave interface is not correctly initialized."); \
|
||||
((mbs_controller_iface_t*)pbase->descr.parent); \
|
||||
} \
|
||||
))
|
||||
|
||||
/**
|
||||
* @brief Parameter access event information type
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t time_stamp; /*!< Timestamp of Modbus Event (uS)*/
|
||||
uint16_t mb_offset; /*!< Modbus register offset */
|
||||
mb_event_group_t type; /*!< Modbus event type */
|
||||
uint8_t *address; /*!< Modbus data storage address */
|
||||
size_t size; /*!< Modbus event register size (number of registers)*/
|
||||
} mb_param_info_t;
|
||||
|
||||
/**
|
||||
* @brief Area access type modificator
|
||||
*/
|
||||
typedef enum {
|
||||
MB_ACCESS_RW = 0x0000,
|
||||
MB_ACCESS_RO = 0x0001,
|
||||
MB_ACCESS_WO = 0x0002
|
||||
} mb_param_access_t;
|
||||
|
||||
/**
|
||||
* @brief Parameter storage area descriptor
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t start_offset; /*!< Modbus start address for area descriptor */
|
||||
mb_param_type_t type; /*!< Type of storage area descriptor */
|
||||
mb_param_access_t access; /*!< Area access type */
|
||||
void *address; /*!< Instance address for storage area descriptor */
|
||||
size_t size; /*!< Instance size for area descriptor (bytes) */
|
||||
} mb_register_area_descriptor_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus Slave controller and stack for TCP port
|
||||
*
|
||||
* @param[out] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] config - pointer to configuration structure for the slave
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
* - ESP_ERR_NOT_SUPPORTED Port type not supported
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_slave_create_tcp(mb_communication_info_t *config, void **ctx);
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus Slave controller and stack for Serial port
|
||||
*
|
||||
* @param[out] ctx context pointer of the initialized modbus interface
|
||||
* @param[in] config - pointer to configuration structure for the slave
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
* - ESP_ERR_NOT_SUPPORTED Port type not supported
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_slave_create_serial(mb_communication_info_t *config, void **ctx);
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus Slave controller interface handle
|
||||
*
|
||||
* @param[in] ctx - pointer to slave interface data structure
|
||||
*/
|
||||
void mbc_slave_init_iface(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Deletes Modbus controller and stack engine
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Parameter error
|
||||
*/
|
||||
esp_err_t mbc_slave_delete(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Critical section lock function for parameter access
|
||||
*
|
||||
* @param[in] ctx pointer to slave handle (modbus interface)
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_slave_lock(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Critical section unlock for parameter access
|
||||
*
|
||||
* @param[in] ctx pointer to slave handle (modbus interface)
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_STATE Initialization failure
|
||||
*/
|
||||
esp_err_t mbc_slave_unlock(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Start of Modbus communication stack
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Modbus stack start error
|
||||
*/
|
||||
esp_err_t mbc_slave_start(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Stop of Modbus communication stack
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Modbus stack stop error
|
||||
*/
|
||||
esp_err_t mbc_slave_stop(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Wait for specific event on parameter change.
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface
|
||||
* @param group Group event bit mask to wait for change
|
||||
*
|
||||
* @return
|
||||
* - mb_event_group_t event bits triggered
|
||||
*/
|
||||
mb_event_group_t mbc_slave_check_event(void *ctx, mb_event_group_t group);
|
||||
|
||||
/**
|
||||
* @brief Get parameter information
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface *
|
||||
* @param[out] reg_info parameter info structure
|
||||
* @param[in] timeout Timeout in milliseconds to read information from
|
||||
* parameter queue
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_TIMEOUT Can not get data from parameter queue
|
||||
* or queue overflow
|
||||
*/
|
||||
esp_err_t mbc_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Set Modbus area descriptor
|
||||
*
|
||||
* @param[in] ctx context pointer of the initialized modbus interface *
|
||||
* @param descr_data Modbus registers area descriptor structure
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: The appropriate descriptor is set
|
||||
* - ESP_ERR_INVALID_ARG: The argument is incorrect
|
||||
*/
|
||||
esp_err_t mbc_slave_set_descriptor(void *ctx, mb_register_area_descriptor_t descr_data);
|
||||
|
||||
/**
|
||||
* @brief Holding register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_regs - number of registers
|
||||
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_holding_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Input register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_regs - number of registers
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_input_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Discrete register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_discrete - number of discrete registers
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_discrete_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete) __attribute__ ((weak));
|
||||
|
||||
/**
|
||||
* @brief Coil register read/write callback function
|
||||
*
|
||||
* @param[in] inst the pointer of the initialized modbus base
|
||||
* @param[in] reg_buffer input buffer of registers
|
||||
* @param[in] address - start address of register
|
||||
* @param[in] n_coils - number of discrete registers
|
||||
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
|
||||
*
|
||||
* @return
|
||||
* - MB_ENOERR: Read write is successful
|
||||
* - MB_ENOREG: The argument is incorrect
|
||||
*/
|
||||
mb_err_enum_t mbc_reg_coils_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode) __attribute__ ((weak));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
556
modbus/mb_controller/common/include/mb_endianness_utils.h
Normal file
556
modbus/mb_controller/common/include/mb_endianness_utils.h
Normal file
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Defines the constant values based on native compiler byte ordering.
|
||||
*/
|
||||
#define MB_BO16_0 0
|
||||
#define MB_BO16_1 1
|
||||
|
||||
#define MB_BO32_0 0
|
||||
#define MB_BO32_1 1
|
||||
#define MB_BO32_2 2
|
||||
#define MB_BO32_3 3
|
||||
|
||||
#define MB_BO64_0 0
|
||||
#define MB_BO64_1 1
|
||||
#define MB_BO64_2 2
|
||||
#define MB_BO64_3 3
|
||||
#define MB_BO64_4 4
|
||||
#define MB_BO64_5 5
|
||||
#define MB_BO64_6 6
|
||||
#define MB_BO64_7 7
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The sized array types used for mapping of extended values
|
||||
*/
|
||||
typedef uint8_t val_16_arr[2];
|
||||
typedef uint8_t val_32_arr[4];
|
||||
typedef uint8_t val_64_arr[8];
|
||||
|
||||
/**
|
||||
* @brief Get int8_t (low byte) value represenatation from register
|
||||
*
|
||||
* @return
|
||||
* - int8_t value of converted from register value
|
||||
*/
|
||||
int8_t mb_get_int8_a(val_16_arr *pi16);
|
||||
|
||||
/**
|
||||
* @brief Set i8 value to the register value pointed by pi16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the actual hex value of the register
|
||||
*/
|
||||
uint16_t mb_set_int8_a(val_16_arr *pi16, int8_t i8);
|
||||
|
||||
/**
|
||||
* @brief Get int8_t (high byte) value from the register value pointed by pi16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the actual hex value of the register
|
||||
*/
|
||||
int8_t mb_get_int8_b(val_16_arr *pi16);
|
||||
|
||||
/**
|
||||
* @brief Set i8 (high byte) value from the register value pointed by pi16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the actual hex value of the register
|
||||
*/
|
||||
uint16_t mb_set_int8_b(val_16_arr *pi16, int8_t i8);
|
||||
|
||||
/**
|
||||
* @brief Get uint8_t (low byte) value represenatation from register poined by pu16
|
||||
*
|
||||
* @return
|
||||
* - uint8_t the value of converted from register value
|
||||
*/
|
||||
uint8_t mb_get_uint8_a(val_16_arr *pu16);
|
||||
|
||||
/**
|
||||
* @brief Set u8 (low byte) value into the register value pointed by pu16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t the value which represents the actual hex value of the register
|
||||
*/
|
||||
uint16_t mb_set_uint8_a(val_16_arr *pu16, uint8_t u8);
|
||||
|
||||
/**
|
||||
* @brief Get uint8_t (high byte) value from the register value pointed by pu16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t the value which represents the actual hex value of the register
|
||||
*/
|
||||
uint8_t mb_get_uint8_b(val_16_arr *pu16);
|
||||
|
||||
/**
|
||||
* @brief Set u8 (high byte) value into the register value pointed by pu16
|
||||
*
|
||||
* @return
|
||||
* - uint16_t the value which represents the actual hex value of the register
|
||||
*/
|
||||
uint16_t mb_set_uint8_b(val_16_arr *pu16, uint8_t u8);
|
||||
|
||||
/**
|
||||
* @brief Get int16_t value from the register value pointed by pu16 with ab endianness
|
||||
*
|
||||
* @return
|
||||
* - int16_t the value which represents the converted value from register
|
||||
*/
|
||||
int16_t mb_get_int16_ab(val_16_arr *pi16);
|
||||
|
||||
/**
|
||||
* @brief Set i16 value to the register pointed by pi16 with ab endianness
|
||||
*
|
||||
* @return
|
||||
* - int16_t the value which represents the converted value from register
|
||||
*/
|
||||
uint16_t mb_set_int16_ab(val_16_arr *pi16, int16_t i16);
|
||||
|
||||
/**
|
||||
* @brief Get uint16_t value from the register value pointed by pu16 with ab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the converted register value
|
||||
*/
|
||||
uint16_t mb_get_uint16_ab(val_16_arr *pu16);
|
||||
|
||||
/**
|
||||
* @brief Set u16 value to the register pointed by pu16 with ab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the converted value from register
|
||||
*/
|
||||
uint16_t mb_set_uint16_ab(val_16_arr *pu16, uint16_t u16);
|
||||
|
||||
/**
|
||||
* @brief Get int16_t value from the register value pointed by pu16 with ba endianness
|
||||
*
|
||||
* @return
|
||||
* - int16_t value which represents the converted register value
|
||||
*/
|
||||
int16_t mb_get_int16_ba(val_16_arr *pi16);
|
||||
|
||||
/**
|
||||
* @brief Set i16 value to the register pointed by pi16 with ba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the converted value from register
|
||||
*/
|
||||
uint16_t mb_set_int16_ba(val_16_arr *pi16, int16_t i16);
|
||||
|
||||
/**
|
||||
* @brief Get uint16_t value from the register value pointed by pu16 with ba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the converted register value
|
||||
*/
|
||||
uint16_t mb_get_uint16_ba(val_16_arr *pu16);
|
||||
|
||||
/**
|
||||
* @brief Set u16 value to the register pointed by pu16 with ba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint16_t value which represents the converted value from register
|
||||
*/
|
||||
uint16_t mb_set_uint16_ba(val_16_arr *pu16, uint16_t u16);
|
||||
|
||||
/**
|
||||
* @brief Get int32_t value from the register value pointed by pi32 with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - int32_t value which represents the converted register value
|
||||
*/
|
||||
int32_t mb_get_int32_abcd(val_32_arr *pi32);
|
||||
|
||||
/**
|
||||
* @brief Set i32 value to the register pointed by pi32 with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_int32_abcd(val_32_arr *pi32, int32_t i32);
|
||||
|
||||
/**
|
||||
* @brief Get uint32_t value from the register value pointed by pu32 with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted register value
|
||||
*/
|
||||
uint32_t mb_get_uint32_abcd(val_32_arr *pu32);
|
||||
|
||||
/**
|
||||
* @brief Set u32 value to the register pointed by pu32 with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_uint32_abcd(val_32_arr *pu32, uint32_t u32);
|
||||
|
||||
/**
|
||||
* @brief Get int32_t value from the register value pointed by pi32 with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - int32_t value which represents the converted register value
|
||||
*/
|
||||
int32_t mb_get_int32_badc(val_32_arr *pi32);
|
||||
|
||||
/**
|
||||
* @brief Set i32 value to the register pointed by pi32 with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_int32_badc(val_32_arr *pi32, int32_t i32);
|
||||
|
||||
/**
|
||||
* @brief Get uint32_t value from the register value pointed by pu32 with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - unt32_t value which represents the converted register value
|
||||
*/
|
||||
uint32_t mb_get_uint32_badc(val_32_arr *pu32);
|
||||
|
||||
/**
|
||||
* @brief Set u32 value to the register pointed by pu32 with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_uint32_badc(val_32_arr *pu32, uint32_t u32);
|
||||
|
||||
/**
|
||||
* @brief Get int32_t value from the register value pointed by pi32 with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - int32_t value which represents the converted register value
|
||||
*/
|
||||
int32_t mb_get_int32_cdab(val_32_arr *pi32);
|
||||
|
||||
/**
|
||||
* @brief Set i32 value to the register pointed by pi32 with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_int32_cdab(val_32_arr *pi32, int32_t i32);
|
||||
|
||||
/**
|
||||
* @brief Get uint32_t value from the register value pointed by pu32 with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - int32_t value which represents the converted register value
|
||||
*/
|
||||
uint32_t mb_get_uint32_cdab(val_32_arr *pu32);
|
||||
|
||||
/**
|
||||
* @brief Set u32 value to the register pointed by pu32 with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_uint32_cdab(val_32_arr *pu32, uint32_t u32);
|
||||
|
||||
/**
|
||||
* @brief Get int32_t value from the register value pointed by pi32 with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - int32_t value which represents the converted register value
|
||||
*/
|
||||
int32_t mb_get_int32_dcba(val_32_arr *pi32);
|
||||
|
||||
/**
|
||||
* @brief Set i32 value to the register pointed by pi32 with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_int32_dcba(val_32_arr *pi32, int32_t i32);
|
||||
|
||||
/**
|
||||
* @brief Get uint32_t value from the register value pointed by pu32 with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted register value
|
||||
*/
|
||||
uint32_t mb_get_uint32_dcba(val_32_arr *pu32);
|
||||
|
||||
/**
|
||||
* @brief Set u32 value to the register pointed by pu32 with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_uint32_dcba(val_32_arr *pu32, uint32_t u32);
|
||||
|
||||
/**
|
||||
* @brief Get float value from the register pointed by pf with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - float value which represents the converted register value
|
||||
*/
|
||||
float mb_get_float_abcd(val_32_arr *pf);
|
||||
|
||||
/**
|
||||
* @brief Set f value to the register pointed by pf with abcd endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_float_abcd(val_32_arr *pf, float f);
|
||||
|
||||
/**
|
||||
* @brief Get float value from the register pointed by pf with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - float value which represents the converted register value
|
||||
*/
|
||||
float mb_get_float_badc(val_32_arr *pf);
|
||||
|
||||
/**
|
||||
* @brief Set f value to the register pointed by pf with badc endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_float_badc(val_32_arr *pf, float f);
|
||||
|
||||
/**
|
||||
* @brief Get float value from the register pointed by pf with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - float value which represents the converted register value
|
||||
*/
|
||||
float mb_get_float_cdab(val_32_arr *pf);
|
||||
|
||||
/**
|
||||
* @brief Set f value to the register pointed by pf with cdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_float_cdab(val_32_arr *pf, float f);
|
||||
|
||||
/**
|
||||
* @brief Get float value from the register pointed by pf with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - float value which represents the converted register value
|
||||
*/
|
||||
float mb_get_float_dcba(val_32_arr *pf);
|
||||
|
||||
/**
|
||||
* @brief Set f value to the register pointed by pf with dcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint32_t value which represents the converted value from register
|
||||
*/
|
||||
uint32_t mb_set_float_dcba(val_32_arr *pf, float f);
|
||||
|
||||
/**
|
||||
* @brief Get double value from the register pointed by pd with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - double value which represents the converted register value
|
||||
*/
|
||||
double mb_get_double_abcdefgh(val_64_arr *pd);
|
||||
|
||||
/**
|
||||
* @brief Set d value to the register pointed by pd with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_double_abcdefgh(val_64_arr *pd, double d);
|
||||
|
||||
/**
|
||||
* @brief Get double value from the register pointed by pd with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - double value which represents the converted register value
|
||||
*/
|
||||
double mb_get_double_hgfedcba(val_64_arr *pd);
|
||||
|
||||
/**
|
||||
* @brief Set d value to the register pointed by pd with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_double_hgfedcba(val_64_arr *pd, double d);
|
||||
|
||||
/**
|
||||
* @brief Get double value from the register pointed by pd with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - double value which represents the converted register value
|
||||
*/
|
||||
double mb_get_double_ghefcdab(val_64_arr *pd);
|
||||
|
||||
/**
|
||||
* @brief Set d value to the register pointed by pd with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_double_ghefcdab(val_64_arr *pd, double d);
|
||||
|
||||
/**
|
||||
* @brief Get double value from the register pointed by pd with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - double value which represents the converted register value
|
||||
*/
|
||||
double mb_get_double_badcfehg(val_64_arr *pd);
|
||||
|
||||
/**
|
||||
* @brief Set d value to the register pointed by pd with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_double_badcfehg(val_64_arr *pd, double d);
|
||||
|
||||
/**
|
||||
* @brief Get int64_t value from the register pointed by pi64 with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - int64_t value which represents the converted register value
|
||||
*/
|
||||
int64_t mb_get_int64_abcdefgh(val_64_arr *pi64);
|
||||
|
||||
/**
|
||||
* @brief Set i value to the register pointed by pi with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_int64_abcdefgh(val_64_arr *pi, int64_t i);
|
||||
|
||||
/**
|
||||
* @brief Get int64_t value from the register pointed by pi64 with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - int64_t value which represents the converted register value
|
||||
*/
|
||||
int64_t mb_get_int64_ghefcdab(val_64_arr *pi64);
|
||||
|
||||
/**
|
||||
* @brief Set i value to the register pointed by pi with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_int64_ghefcdab(val_64_arr *pi, int64_t i);
|
||||
|
||||
/**
|
||||
* @brief Get int64_t value from the register pointed by pi64 with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - int64_t value which represents the converted register value
|
||||
*/
|
||||
int64_t mb_get_int64_hgfedcba(val_64_arr *pi64);
|
||||
|
||||
/**
|
||||
* @brief Set i value to the register pointed by pi with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_int64_hgfedcba(val_64_arr *pi, int64_t i);
|
||||
|
||||
/**
|
||||
* @brief Get int64_t value from the register pointed by pi64 with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - int64_t value which represents the converted register value
|
||||
*/
|
||||
int64_t mb_get_int64_badcfehg(val_64_arr *pi64);
|
||||
|
||||
/**
|
||||
* @brief Set i value to the register pointed by pi with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_int64_badcfehg(val_64_arr *pi, int64_t i);
|
||||
|
||||
/**
|
||||
* @brief Get uint64_t value from the register pointed by pui with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted register value
|
||||
*/
|
||||
uint64_t mb_get_uint64_abcdefgh(val_64_arr *pui);
|
||||
|
||||
/**
|
||||
* @brief Set ui value to the register pointed by pi with abcdefgh endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_uint64_abcdefgh(val_64_arr *pui, uint64_t ui);
|
||||
|
||||
/**
|
||||
* @brief Get uint64_t value from the register pointed by pui with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted register value
|
||||
*/
|
||||
uint64_t mb_get_uint64_hgfedcba(val_64_arr *pui);
|
||||
|
||||
/**
|
||||
* @brief Set ui value to the register pointed by pui with hgfedcba endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_uint64_hgfedcba(val_64_arr *pui, uint64_t ui);
|
||||
|
||||
/**
|
||||
* @brief Get uint64_t value from the register pointed by pui with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted register value
|
||||
*/
|
||||
uint64_t mb_get_uint64_ghefcdab(val_64_arr *pui);
|
||||
|
||||
/**
|
||||
* @brief Set ui value to the register pointed by pui with ghefcdab endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_uint64_ghefcdab(val_64_arr *pui, uint64_t ui);
|
||||
|
||||
/**
|
||||
* @brief Get uint64_t value from the register pointed by pui with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted register value
|
||||
*/
|
||||
uint64_t mb_get_uint64_badcfehg(val_64_arr *pui);
|
||||
|
||||
/**
|
||||
* @brief Set ui value to the register pointed by pui with badcfehg endianness
|
||||
*
|
||||
* @return
|
||||
* - uint64_t value which represents the converted value from register
|
||||
*/
|
||||
uint64_t mb_set_uint64_badcfehg(val_64_arr *pui, uint64_t ui);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
20
modbus/mb_controller/common/include/mbcontroller.h
Normal file
20
modbus/mb_controller/common/include/mbcontroller.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "string.h" // for strerror()
|
||||
#include "errno.h" // for errno
|
||||
#include "esp_err.h" // for error handling
|
||||
#include "driver/uart.h" // for uart port number defines
|
||||
#include "sdkconfig.h" // for KConfig options
|
||||
|
||||
#include "esp_modbus_master.h"
|
||||
#include "esp_modbus_slave.h"
|
||||
|
||||
|
683
modbus/mb_controller/common/mb_endianness_utils.c
Normal file
683
modbus/mb_controller/common/mb_endianness_utils.c
Normal file
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "mb_endianness_utils.h"
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
|
||||
static INLINE int16_t mb_get_int16_generic(int n0, int n1, val_16_arr *psrc)
|
||||
{
|
||||
val_16_arr *pv = psrc;
|
||||
union {
|
||||
val_16_arr arr;
|
||||
int16_t value;
|
||||
} bov;
|
||||
bov.arr[n0] = (*pv)[MB_BO16_0];
|
||||
bov.arr[n1] = (*pv)[MB_BO16_1];
|
||||
return (bov.value);
|
||||
}
|
||||
|
||||
static INLINE uint16_t mb_get_uint16_generic(int n0, int n1, val_16_arr *psrc)
|
||||
{
|
||||
val_16_arr *pv = psrc;
|
||||
union {
|
||||
val_16_arr arr;
|
||||
uint16_t value;
|
||||
} bov;
|
||||
bov.arr[n0] = (*pv)[MB_BO16_0];
|
||||
bov.arr[n1] = (*pv)[MB_BO16_1];
|
||||
return (bov.value);
|
||||
}
|
||||
|
||||
static INLINE uint16_t mb_set_uint16_generic(int n0, int n1, val_16_arr *pdest, uint16_t val)
|
||||
{
|
||||
val_16_arr *pv = pdest;
|
||||
union {
|
||||
val_16_arr arr;
|
||||
uint16_t value;
|
||||
} bov;
|
||||
bov.value = val;
|
||||
(*pv)[MB_BO16_0] = bov.arr[n0];
|
||||
(*pv)[MB_BO16_1] = bov.arr[n1];
|
||||
return (*((uint16_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE int16_t mb_set_int16_generic(int n0, int n1, val_16_arr *pdest, int16_t val)
|
||||
{
|
||||
val_16_arr *pv = pdest;
|
||||
union {
|
||||
val_16_arr arr;
|
||||
int16_t value;
|
||||
} bov;
|
||||
bov.value = val;
|
||||
(*pv)[MB_BO16_0] = bov.arr[n0];
|
||||
(*pv)[MB_BO16_1] = bov.arr[n1];
|
||||
return (*((uint16_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE uint32_t mb_get_uint32_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
|
||||
{
|
||||
val_32_arr *pv = psrc;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
uint32_t value;
|
||||
} bov;
|
||||
bov.arr[n0] = (*pv)[MB_BO32_0];
|
||||
bov.arr[n1] = (*pv)[MB_BO32_1];
|
||||
bov.arr[n2] = (*pv)[MB_BO32_2];
|
||||
bov.arr[n3] = (*pv)[MB_BO32_3];
|
||||
return (bov.value);
|
||||
}
|
||||
|
||||
static INLINE int32_t mb_get_int32_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
|
||||
{
|
||||
val_32_arr *pv = psrc;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
int32_t value;
|
||||
} bov;
|
||||
bov.arr[n0] = (*pv)[MB_BO32_0];
|
||||
bov.arr[n1] = (*pv)[MB_BO32_1];
|
||||
bov.arr[n2] = (*pv)[MB_BO32_2];
|
||||
bov.arr[n3] = (*pv)[MB_BO32_3];
|
||||
return (bov.value);
|
||||
}
|
||||
|
||||
static INLINE float mb_get_float_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
|
||||
{
|
||||
val_32_arr *pv = psrc;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
float value;
|
||||
} bov;
|
||||
bov.arr[n0] = (*pv)[MB_BO32_0];
|
||||
bov.arr[n1] = (*pv)[MB_BO32_1];
|
||||
bov.arr[n2] = (*pv)[MB_BO32_2];
|
||||
bov.arr[n3] = (*pv)[MB_BO32_3];
|
||||
return (bov.value);
|
||||
}
|
||||
|
||||
static INLINE uint32_t mb_set_int32_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, int32_t val)
|
||||
{
|
||||
val_32_arr *pv = pdest;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
int32_t value;
|
||||
} bov;
|
||||
bov.value = val;
|
||||
(*pv)[MB_BO32_0] = bov.arr[n0];
|
||||
(*pv)[MB_BO32_1] = bov.arr[n1];
|
||||
(*pv)[MB_BO32_2] = bov.arr[n2];
|
||||
(*pv)[MB_BO32_3] = bov.arr[n3];
|
||||
return (*((uint32_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE uint32_t mb_set_uint32_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, uint32_t val)
|
||||
{
|
||||
val_32_arr *pv = pdest;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
uint32_t value;
|
||||
} bov;
|
||||
bov.value = val;
|
||||
(*pv)[MB_BO32_0] = bov.arr[n0];
|
||||
(*pv)[MB_BO32_1] = bov.arr[n1];
|
||||
(*pv)[MB_BO32_2] = bov.arr[n2];
|
||||
(*pv)[MB_BO32_3] = bov.arr[n3];
|
||||
return (*((uint32_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE uint32_t mb_set_float_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, float val)
|
||||
{
|
||||
val_32_arr *pv = pdest;
|
||||
union {
|
||||
val_32_arr arr;
|
||||
float value;
|
||||
} bov;
|
||||
bov.value = val;
|
||||
(*pv)[MB_BO32_0] = bov.arr[n0];
|
||||
(*pv)[MB_BO32_1] = bov.arr[n1];
|
||||
(*pv)[MB_BO32_2] = bov.arr[n2];
|
||||
(*pv)[MB_BO32_3] = bov.arr[n3];
|
||||
return (*((uint32_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE int64_t mb_get_int64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
|
||||
{
|
||||
val_64_arr *pv64 = psrc;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
int64_t value;
|
||||
} bo64;
|
||||
bo64.arr[n0] = (*pv64)[MB_BO64_0];
|
||||
bo64.arr[n1] = (*pv64)[MB_BO64_1];
|
||||
bo64.arr[n2] = (*pv64)[MB_BO64_2];
|
||||
bo64.arr[n3] = (*pv64)[MB_BO64_3];
|
||||
bo64.arr[n4] = (*pv64)[MB_BO64_4];
|
||||
bo64.arr[n5] = (*pv64)[MB_BO64_5];
|
||||
bo64.arr[n6] = (*pv64)[MB_BO64_6];
|
||||
bo64.arr[n7] = (*pv64)[MB_BO64_7];
|
||||
return (bo64.value);
|
||||
}
|
||||
|
||||
static INLINE uint64_t mb_get_uint64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
|
||||
{
|
||||
val_64_arr *pv64 = psrc;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
uint64_t value;
|
||||
} bo64;
|
||||
bo64.arr[n0] = (*pv64)[MB_BO64_0];
|
||||
bo64.arr[n1] = (*pv64)[MB_BO64_1];
|
||||
bo64.arr[n2] = (*pv64)[MB_BO64_2];
|
||||
bo64.arr[n3] = (*pv64)[MB_BO64_3];
|
||||
bo64.arr[n4] = (*pv64)[MB_BO64_4];
|
||||
bo64.arr[n5] = (*pv64)[MB_BO64_5];
|
||||
bo64.arr[n6] = (*pv64)[MB_BO64_6];
|
||||
bo64.arr[n7] = (*pv64)[MB_BO64_7];
|
||||
return (bo64.value);
|
||||
}
|
||||
|
||||
static INLINE double mb_get_double_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
|
||||
{
|
||||
val_64_arr *pv64 = psrc;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
double value;
|
||||
} bo64;
|
||||
bo64.arr[n0] = (*pv64)[MB_BO64_0];
|
||||
bo64.arr[n1] = (*pv64)[MB_BO64_1];
|
||||
bo64.arr[n2] = (*pv64)[MB_BO64_2];
|
||||
bo64.arr[n3] = (*pv64)[MB_BO64_3];
|
||||
bo64.arr[n4] = (*pv64)[MB_BO64_4];
|
||||
bo64.arr[n5] = (*pv64)[MB_BO64_5];
|
||||
bo64.arr[n6] = (*pv64)[MB_BO64_6];
|
||||
bo64.arr[n7] = (*pv64)[MB_BO64_7];
|
||||
return (bo64.value);
|
||||
}
|
||||
|
||||
static INLINE uint64_t mb_set_int64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, int64_t val)
|
||||
{
|
||||
val_64_arr *pv = pdest;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
int64_t value;
|
||||
} bo64;
|
||||
bo64.value = val;
|
||||
(*pv)[MB_BO64_0] = bo64.arr[n0];
|
||||
(*pv)[MB_BO64_1] = bo64.arr[n1];
|
||||
(*pv)[MB_BO64_2] = bo64.arr[n2];
|
||||
(*pv)[MB_BO64_3] = bo64.arr[n3];
|
||||
(*pv)[MB_BO64_4] = bo64.arr[n4];
|
||||
(*pv)[MB_BO64_5] = bo64.arr[n5];
|
||||
(*pv)[MB_BO64_6] = bo64.arr[n6];
|
||||
(*pv)[MB_BO64_7] = bo64.arr[n7];
|
||||
return (*((uint64_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE uint64_t mb_set_uint64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, uint64_t val)
|
||||
{
|
||||
val_64_arr *pv = pdest;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
uint64_t value;
|
||||
} bo64;
|
||||
bo64.value = val;
|
||||
(*pv)[MB_BO64_0] = bo64.arr[n0];
|
||||
(*pv)[MB_BO64_1] = bo64.arr[n1];
|
||||
(*pv)[MB_BO64_2] = bo64.arr[n2];
|
||||
(*pv)[MB_BO64_3] = bo64.arr[n3];
|
||||
(*pv)[MB_BO64_4] = bo64.arr[n4];
|
||||
(*pv)[MB_BO64_5] = bo64.arr[n5];
|
||||
(*pv)[MB_BO64_6] = bo64.arr[n6];
|
||||
(*pv)[MB_BO64_7] = bo64.arr[n7];
|
||||
return (*((uint64_t *)pv));
|
||||
}
|
||||
|
||||
static INLINE uint64_t mb_set_double_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, double val)
|
||||
{
|
||||
val_64_arr *pv = pdest;
|
||||
union {
|
||||
val_64_arr arr;
|
||||
double value;
|
||||
} bo64;
|
||||
bo64.value = val;
|
||||
(*pv)[MB_BO64_0] = bo64.arr[n0];
|
||||
(*pv)[MB_BO64_1] = bo64.arr[n1];
|
||||
(*pv)[MB_BO64_2] = bo64.arr[n2];
|
||||
(*pv)[MB_BO64_3] = bo64.arr[n3];
|
||||
(*pv)[MB_BO64_4] = bo64.arr[n4];
|
||||
(*pv)[MB_BO64_5] = bo64.arr[n5];
|
||||
(*pv)[MB_BO64_6] = bo64.arr[n6];
|
||||
(*pv)[MB_BO64_7] = bo64.arr[n7];
|
||||
return (*((uint64_t *)pv));
|
||||
}
|
||||
|
||||
int8_t mb_get_int8_a(pi16)
|
||||
val_16_arr *pi16;
|
||||
{
|
||||
return((int8_t)(*pi16)[MB_BO16_0]);
|
||||
}
|
||||
|
||||
uint16_t mb_set_int8_a(pi16, i8)
|
||||
val_16_arr *pi16;
|
||||
int8_t i8;
|
||||
{
|
||||
(*pi16)[MB_BO16_0] = (char)i8;
|
||||
(*pi16)[MB_BO16_1] = 0;
|
||||
return (*((uint16_t *)pi16));
|
||||
}
|
||||
|
||||
int8_t mb_get_int8_b(pi16)
|
||||
val_16_arr *pi16;
|
||||
{
|
||||
return((int8_t)(*pi16)[MB_BO16_1]);
|
||||
}
|
||||
|
||||
uint16_t mb_set_int8_b(pi16, i8)
|
||||
val_16_arr *pi16;
|
||||
int8_t i8;
|
||||
{
|
||||
(*pi16)[MB_BO16_0] = 0;
|
||||
(*pi16)[MB_BO16_1] = (char)i8;
|
||||
return (*((uint16_t *)pi16));
|
||||
}
|
||||
|
||||
uint8_t mb_get_uint8_a(pu16)
|
||||
val_16_arr *pu16;
|
||||
{
|
||||
return((uint8_t)(*pu16)[MB_BO16_0]);
|
||||
}
|
||||
|
||||
uint16_t mb_set_uint8_a(pu16, u8)
|
||||
val_16_arr *pu16;
|
||||
uint8_t u8;
|
||||
{
|
||||
(*pu16)[MB_BO16_0] = (char)u8;
|
||||
(*pu16)[MB_BO16_1] = 0;
|
||||
return (*((uint16_t *)pu16));
|
||||
}
|
||||
|
||||
uint8_t mb_get_uint8_b(pu16)
|
||||
val_16_arr *pu16;
|
||||
{
|
||||
return((uint8_t)(*pu16)[MB_BO16_1]);
|
||||
}
|
||||
|
||||
uint16_t mb_set_uint8_b(pu16, u8)
|
||||
val_16_arr *pu16;
|
||||
uint8_t u8;
|
||||
{
|
||||
(*pu16)[MB_BO16_0] = 0;
|
||||
(*pu16)[MB_BO16_1] = (char)u8;
|
||||
return (*((uint16_t *)pu16));
|
||||
}
|
||||
|
||||
int16_t mb_get_int16_ab(pi16)
|
||||
val_16_arr *pi16;
|
||||
{
|
||||
return mb_get_int16_generic(0, 1, pi16);
|
||||
}
|
||||
|
||||
uint16_t mb_set_int16_ab(pi16, i16)
|
||||
val_16_arr *pi16;
|
||||
int16_t i16;
|
||||
{
|
||||
return mb_set_int16_generic(0, 1, pi16, i16);
|
||||
}
|
||||
|
||||
uint16_t mb_get_uint16_ab(pu16)
|
||||
val_16_arr *pu16;
|
||||
{
|
||||
return mb_get_uint16_generic(0, 1, pu16);
|
||||
}
|
||||
|
||||
uint16_t mb_set_uint16_ab(pu16, u16)
|
||||
val_16_arr *pu16;
|
||||
uint16_t u16;
|
||||
{
|
||||
return mb_set_uint16_generic(0, 1, pu16, u16);
|
||||
}
|
||||
|
||||
int16_t mb_get_int16_ba(pi16)
|
||||
val_16_arr *pi16;
|
||||
{
|
||||
return mb_get_int16_generic(1, 0, pi16);
|
||||
}
|
||||
|
||||
uint16_t mb_set_int16_ba(pi16, i16)
|
||||
val_16_arr *pi16;
|
||||
int16_t i16;
|
||||
{
|
||||
return mb_set_int16_generic(1, 0, pi16, i16);
|
||||
}
|
||||
|
||||
uint16_t mb_get_uint16_ba(pu16)
|
||||
val_16_arr *pu16;
|
||||
{
|
||||
return mb_get_int16_generic(1, 0, pu16);
|
||||
}
|
||||
|
||||
uint16_t mb_set_uint16_ba(pu16, u16)
|
||||
val_16_arr *pu16;
|
||||
uint16_t u16;
|
||||
{
|
||||
return mb_set_int16_generic(1, 0, pu16, u16);
|
||||
}
|
||||
|
||||
int32_t mb_get_int32_abcd(pi32)
|
||||
val_32_arr *pi32;
|
||||
{
|
||||
return mb_get_int32_generic(0, 1, 2, 3, pi32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_int32_abcd(pi32, i32)
|
||||
val_32_arr *pi32;
|
||||
int32_t i32;
|
||||
{
|
||||
return mb_set_int32_generic(0, 1, 2, 3, pi32, i32);
|
||||
}
|
||||
|
||||
uint32_t mb_get_uint32_abcd(pu32)
|
||||
val_32_arr *pu32;
|
||||
{
|
||||
return mb_get_uint32_generic(0, 1, 2, 3, pu32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_uint32_abcd(pu32, u32)
|
||||
val_32_arr *pu32;
|
||||
uint32_t u32;
|
||||
{
|
||||
return mb_set_uint32_generic(0, 1, 2, 3, pu32, u32);
|
||||
}
|
||||
|
||||
int32_t mb_get_int32_badc(pi32)
|
||||
val_32_arr *pi32;
|
||||
{
|
||||
return mb_get_int32_generic(1, 0, 3, 2, pi32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_int32_badc(pi32, i32)
|
||||
val_32_arr *pi32;
|
||||
int32_t i32;
|
||||
{
|
||||
return mb_set_int32_generic(1, 0, 3, 2, pi32, i32);
|
||||
}
|
||||
|
||||
uint32_t mb_get_uint32_badc(pu32)
|
||||
val_32_arr *pu32;
|
||||
{
|
||||
return mb_get_uint32_generic(1, 0, 3, 2, pu32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_uint32_badc(pu32, u32)
|
||||
val_32_arr *pu32;
|
||||
uint32_t u32;
|
||||
{
|
||||
return mb_set_uint32_generic(1, 0, 3, 2, pu32, u32);
|
||||
}
|
||||
|
||||
int32_t mb_get_int32_cdab(pi32)
|
||||
val_32_arr *pi32;
|
||||
{
|
||||
return mb_get_int32_generic(2, 3, 0, 1, pi32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_int32_cdab(pi32, i32)
|
||||
val_32_arr *pi32;
|
||||
int32_t i32;
|
||||
{
|
||||
return mb_set_int32_generic(2, 3, 0, 1, pi32, i32);
|
||||
}
|
||||
|
||||
uint32_t mb_get_uint32_cdab(pu32)
|
||||
val_32_arr *pu32;
|
||||
{
|
||||
return mb_get_uint32_generic(2, 3, 0, 1, pu32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_uint32_cdab(pu32, u32)
|
||||
val_32_arr *pu32;
|
||||
uint32_t u32;
|
||||
{
|
||||
return mb_set_uint32_generic(2, 3, 0, 1, pu32, u32);
|
||||
}
|
||||
|
||||
int32_t mb_get_int32_dcba(pi32)
|
||||
val_32_arr *pi32;
|
||||
{
|
||||
return mb_get_int32_generic(3, 2, 1, 0, pi32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_int32_dcba(pi32, i32)
|
||||
val_32_arr *pi32;
|
||||
int32_t i32;
|
||||
{
|
||||
return mb_set_int32_generic(3, 2, 1, 0, pi32, i32);
|
||||
}
|
||||
|
||||
uint32_t mb_get_uint32_dcba(pu32)
|
||||
val_32_arr *pu32;
|
||||
{
|
||||
return mb_get_uint32_generic(3, 2, 1, 0, pu32);
|
||||
}
|
||||
|
||||
uint32_t mb_set_uint32_dcba(pu32, u32)
|
||||
val_32_arr *pu32;
|
||||
uint32_t u32;
|
||||
{
|
||||
return mb_set_uint32_generic(3, 2, 1, 0, pu32, u32);
|
||||
}
|
||||
|
||||
float mb_get_float_abcd(pf)
|
||||
val_32_arr *pf;
|
||||
{
|
||||
return mb_get_float_generic(0, 1, 2, 3, pf);
|
||||
}
|
||||
|
||||
uint32_t mb_set_float_abcd(pf, f)
|
||||
val_32_arr *pf;
|
||||
float f;
|
||||
{
|
||||
return mb_set_float_generic(0, 1, 2, 3, pf, f);
|
||||
}
|
||||
|
||||
float mb_get_float_badc(pf)
|
||||
val_32_arr *pf;
|
||||
{
|
||||
return mb_get_float_generic(1, 0, 3, 2, pf);
|
||||
}
|
||||
|
||||
uint32_t mb_set_float_badc(pf, f)
|
||||
val_32_arr *pf;
|
||||
float f;
|
||||
{
|
||||
return mb_set_float_generic(1, 0, 3, 2, pf, f);
|
||||
}
|
||||
|
||||
float mb_get_float_cdab(pf)
|
||||
val_32_arr *pf;
|
||||
{
|
||||
return mb_get_float_generic(2, 3, 0, 1, pf);
|
||||
}
|
||||
|
||||
uint32_t mb_set_float_cdab(pf, f)
|
||||
val_32_arr *pf;
|
||||
float f;
|
||||
{
|
||||
return mb_set_float_generic(2, 3, 0, 1, pf, f);
|
||||
}
|
||||
|
||||
float mb_get_float_dcba(pf)
|
||||
val_32_arr *pf;
|
||||
{
|
||||
return mb_get_float_generic(3, 2, 1, 0, pf);
|
||||
}
|
||||
|
||||
uint32_t mb_set_float_dcba(pf, f)
|
||||
val_32_arr *pf;
|
||||
float f;
|
||||
{
|
||||
return mb_set_float_generic(3, 2, 1, 0, pf, f);
|
||||
}
|
||||
|
||||
double mb_get_double_abcdefgh(pd)
|
||||
val_64_arr *pd;
|
||||
{
|
||||
return mb_get_double_generic(0, 1, 2, 3, 4, 5, 6, 7, pd);
|
||||
}
|
||||
|
||||
uint64_t mb_set_double_abcdefgh(pd, d)
|
||||
val_64_arr *pd;
|
||||
double d;
|
||||
{
|
||||
return mb_set_double_generic(0, 1, 2, 3, 4, 5, 6, 7, pd, d);
|
||||
}
|
||||
|
||||
double mb_get_double_hgfedcba(pd)
|
||||
val_64_arr *pd;
|
||||
{
|
||||
return mb_get_double_generic(7, 6, 5, 4, 3, 2, 1, 0, pd);
|
||||
}
|
||||
|
||||
uint64_t mb_set_double_hgfedcba(pd, d)
|
||||
val_64_arr *pd;
|
||||
double d;
|
||||
{
|
||||
return mb_set_double_generic(7, 6, 5, 4, 3, 2, 1, 0, pd, d);
|
||||
}
|
||||
|
||||
double mb_get_double_ghefcdab(pd)
|
||||
val_64_arr *pd;
|
||||
{
|
||||
return mb_get_double_generic(6, 7, 4, 5, 2, 3, 0, 1, pd);
|
||||
}
|
||||
|
||||
uint64_t mb_set_double_ghefcdab(pd, d)
|
||||
val_64_arr *pd;
|
||||
double d;
|
||||
{
|
||||
return mb_set_double_generic(6, 7, 4, 5, 2, 3, 0, 1, pd, d);
|
||||
}
|
||||
|
||||
double mb_get_double_badcfehg(pd)
|
||||
val_64_arr *pd;
|
||||
{
|
||||
return mb_get_double_generic(1, 0, 3, 2, 5, 4, 7, 6, pd);
|
||||
}
|
||||
|
||||
uint64_t mb_set_double_badcfehg(pd, d)
|
||||
val_64_arr *pd;
|
||||
double d;
|
||||
{
|
||||
return mb_set_double_generic(1, 0, 3, 2, 5, 4, 7, 6, pd, d);
|
||||
}
|
||||
|
||||
int64_t mb_get_int64_abcdefgh(pi64)
|
||||
val_64_arr *pi64;
|
||||
{
|
||||
return mb_get_int64_generic(0, 1, 2, 3, 4, 5, 6, 7, pi64);
|
||||
}
|
||||
|
||||
uint64_t mb_set_int64_abcdefgh(pi, i)
|
||||
val_64_arr *pi;
|
||||
int64_t i;
|
||||
{
|
||||
return mb_set_int64_generic(0, 1, 2, 3, 4, 5, 6, 7, pi, i);
|
||||
}
|
||||
|
||||
int64_t mb_get_int64_hgfedcba(pi64)
|
||||
val_64_arr *pi64;
|
||||
{
|
||||
return mb_get_int64_generic(7, 6, 5, 4, 3, 2, 1, 0, pi64);
|
||||
}
|
||||
|
||||
uint64_t mb_set_int64_hgfedcba(pi, i)
|
||||
val_64_arr *pi;
|
||||
int64_t i;
|
||||
{
|
||||
return mb_set_int64_generic(7, 6, 5, 4, 3, 2, 1, 0, pi, i);
|
||||
}
|
||||
|
||||
int64_t mb_get_int64_ghefcdab(pi64)
|
||||
val_64_arr *pi64;
|
||||
{
|
||||
return mb_get_int64_generic(6, 7, 4, 5, 2, 3, 0, 1, pi64);
|
||||
}
|
||||
|
||||
uint64_t mb_set_int64_ghefcdab(pi, i)
|
||||
val_64_arr *pi;
|
||||
int64_t i;
|
||||
{
|
||||
return mb_set_int64_generic(6, 7, 4, 5, 2, 3, 0, 1, pi, i);
|
||||
}
|
||||
|
||||
int64_t mb_get_int64_badcfehg(pi64)
|
||||
val_64_arr *pi64;
|
||||
{
|
||||
return mb_get_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pi64);
|
||||
}
|
||||
|
||||
uint64_t mb_set_int64_badcfehg(pi, i)
|
||||
val_64_arr *pi;
|
||||
int64_t i;
|
||||
{
|
||||
return mb_set_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pi, i);
|
||||
}
|
||||
|
||||
uint64_t mb_get_uint64_abcdefgh(pui)
|
||||
val_64_arr *pui;
|
||||
{
|
||||
return mb_get_uint64_generic(0, 1, 2, 3, 4, 5, 6, 7, pui);
|
||||
}
|
||||
|
||||
uint64_t mb_set_uint64_abcdefgh(pui, ui)
|
||||
val_64_arr *pui;
|
||||
uint64_t ui;
|
||||
{
|
||||
return mb_set_uint64_generic(0, 1, 2, 3, 4, 5, 6, 7, pui, ui);
|
||||
}
|
||||
|
||||
uint64_t mb_get_uint64_hgfedcba(pui)
|
||||
val_64_arr *pui;
|
||||
{
|
||||
return mb_get_uint64_generic(7, 6, 5, 4, 3, 2, 1, 0, pui);
|
||||
}
|
||||
|
||||
uint64_t mb_set_uint64_hgfedcba(pui, ui)
|
||||
val_64_arr *pui;
|
||||
uint64_t ui;
|
||||
{
|
||||
return mb_set_uint64_generic(7, 6, 5, 4, 3, 2, 1, 0, pui, ui);
|
||||
}
|
||||
|
||||
uint64_t mb_get_uint64_ghefcdab(pui)
|
||||
val_64_arr *pui;
|
||||
{
|
||||
return mb_get_uint64_generic(6, 7, 4, 5, 2, 3, 0, 1, pui);
|
||||
}
|
||||
|
||||
uint64_t mb_set_uint64_ghefcdab(pui, ui)
|
||||
val_64_arr *pui;
|
||||
uint64_t ui;
|
||||
{
|
||||
return mb_set_uint64_generic(6, 7, 4, 5, 2, 3, 0, 1, pui, ui);
|
||||
}
|
||||
|
||||
uint64_t mb_get_uint64_badcfehg(pui)
|
||||
val_64_arr *pui;
|
||||
{
|
||||
return mb_get_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pui);
|
||||
}
|
||||
|
||||
uint64_t mb_set_uint64_badcfehg(pui, ui)
|
||||
val_64_arr *pui;
|
||||
uint64_t ui;
|
||||
{
|
||||
return mb_set_uint64_generic(1, 0, 3, 2, 5, 4, 7, 6, pui, ui);
|
||||
}
|
89
modbus/mb_controller/common/mbc_master.h
Normal file
89
modbus/mb_controller/common/mbc_master.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/queue.h> // for list
|
||||
#include "freertos/FreeRTOS.h" // for task creation and queue access
|
||||
#include "freertos/task.h" // for task api access
|
||||
#include "freertos/event_groups.h" // for event groups
|
||||
#include "driver/uart.h" // for UART types
|
||||
#include "errno.h" // for errno
|
||||
#include "esp_log.h" // for log write
|
||||
#include "string.h" // for strerror()
|
||||
#include "esp_modbus_common.h" // for common types
|
||||
#include "esp_modbus_master.h" // for public master types
|
||||
|
||||
#include "mb_common.h" // for mb_base_t
|
||||
#include "mb_utils.h"
|
||||
#include "mb_master.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
|
||||
// Set the maximum resource waiting time, the actual time of resouce release
|
||||
// will be dependent on response time set by timer + convertion time if the command is received
|
||||
#define MB_MAX_RESP_DELAY_MS (3000)
|
||||
|
||||
/**
|
||||
* @brief Request mode for parameter to use in data dictionary
|
||||
*/
|
||||
typedef enum {
|
||||
MB_PARAM_READ, /*!< Read parameter values. */
|
||||
MB_PARAM_WRITE /*!< Write parameter values. */
|
||||
} mb_param_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Modbus controller handler structure
|
||||
*/
|
||||
typedef struct {
|
||||
mb_port_type_t port_type; /*!< Modbus port type */
|
||||
mb_communication_info_t comm_opts; /*!< Modbus communication info */
|
||||
uint8_t *reg_buffer_ptr; /*!< Modbus data buffer pointer */
|
||||
uint16_t reg_buffer_size; /*!< Modbus data buffer size */
|
||||
TaskHandle_t task_handle; /*!< Modbus task handle */
|
||||
EventGroupHandle_t event_group_handle; /*!< Modbus controller event group */
|
||||
const mb_parameter_descriptor_t *param_descriptor_table; /*!< Modbus controller parameter description table */
|
||||
size_t mbm_param_descriptor_size; /*!< Modbus controller parameter description table size */
|
||||
} mb_master_options_t;
|
||||
|
||||
typedef esp_err_t (*iface_get_cid_info_fp)(void *, uint16_t, const mb_parameter_descriptor_t **); /*!< Interface get_cid_info method */
|
||||
typedef esp_err_t (*iface_get_parameter_fp)(void *, uint16_t, uint8_t *, uint8_t *); /*!< Interface get_parameter method */
|
||||
typedef esp_err_t (*iface_get_parameter_with_fp)(void *, uint16_t, uint8_t, uint8_t *, uint8_t *); /*!< Interface get_parameter_with method */
|
||||
typedef esp_err_t (*iface_send_request_fp)(void *, mb_param_request_t*, void *); /*!< Interface send_request method */
|
||||
typedef esp_err_t (*iface_mbm_set_descriptor_fp)(void *, const mb_parameter_descriptor_t*, const uint16_t); /*!< Interface set_descriptor method */
|
||||
typedef esp_err_t (*iface_set_parameter_fp)(void *, uint16_t, uint8_t *, uint8_t *); /*!< Interface set_parameter method */
|
||||
typedef esp_err_t (*iface_set_parameter_with_fp)(void *, uint16_t, uint8_t, uint8_t *, uint8_t *); /*!< Interface set_parameter_with method */
|
||||
|
||||
/**
|
||||
* @brief Modbus controller interface structure
|
||||
*/
|
||||
typedef struct {
|
||||
mb_base_t *mb_base;
|
||||
// Master object interface options
|
||||
mb_master_options_t opts;
|
||||
bool is_active; /*!< Interface is active */
|
||||
|
||||
// Public interface methods
|
||||
iface_create_fp create; /*!< Interface constructor */
|
||||
iface_method_default_fp delete; /*!< Interface method delete */
|
||||
iface_method_default_fp start; /*!< Interface method start */
|
||||
iface_method_default_fp stop; /*!< Interface method stop */
|
||||
iface_get_cid_info_fp get_cid_info; /*!< Interface get_cid_info method */
|
||||
iface_get_parameter_fp get_parameter; /*!< Interface get_parameter method */
|
||||
iface_get_parameter_with_fp get_parameter_with; /*!< Interface get_parameter_with method */
|
||||
iface_send_request_fp send_request; /*!< Interface send_request method */
|
||||
iface_mbm_set_descriptor_fp set_descriptor; /*!< Interface set_descriptor method */
|
||||
iface_set_parameter_fp set_parameter; /*!< Interface set_parameter method */
|
||||
iface_set_parameter_with_fp set_parameter_with; /*!< Interface set_parameter_with method */
|
||||
} mbm_controller_iface_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
78
modbus/mb_controller/common/mbc_slave.h
Normal file
78
modbus/mb_controller/common/mbc_slave.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/uart.h" // for uart defines
|
||||
#include "errno.h" // for errno
|
||||
#include "sys/queue.h" // for list
|
||||
#include "esp_log.h" // for log write
|
||||
#include "string.h" // for strerror()
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "mb_common.h" // for mb_base_t
|
||||
#include "esp_modbus_slave.h" // for public type defines
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
#define MB_INST_MIN_SIZE (1) // The minimal size of Modbus registers area in bytes
|
||||
#define MB_INST_MAX_SIZE (65535 * 2) // The maximum size of Modbus area in bytes
|
||||
|
||||
#define MB_CONTROLLER_NOTIFY_QUEUE_SIZE (CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE) // Number of messages in parameter notification queue
|
||||
#define MB_CONTROLLER_NOTIFY_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT)) // notification timeout
|
||||
|
||||
/**
|
||||
* @brief Modbus area descriptor list item
|
||||
*/
|
||||
typedef struct mb_descr_entry_s {
|
||||
uint16_t start_offset; /*!< Modbus start address for area descriptor */
|
||||
mb_param_type_t type; /*!< Type of storage area descriptor */
|
||||
mb_param_access_t access; /*!< Area access type */
|
||||
void *p_data; /*!< Instance address for storage area descriptor */
|
||||
size_t size; /*!< Instance size for area descriptor (bytes) */
|
||||
LIST_ENTRY(mb_descr_entry_s) entries; /*!< The Modbus area descriptor entry */
|
||||
} mb_descr_entry_t;
|
||||
|
||||
/**
|
||||
* @brief Modbus controller handler structure
|
||||
*/
|
||||
typedef struct {
|
||||
mb_port_type_t port_type; /*!< port type */
|
||||
mb_communication_info_t comm_opts; /*!< communication info */
|
||||
TaskHandle_t task_handle; /*!< task handle */
|
||||
EventGroupHandle_t event_group_handle; /*!< controller event group */
|
||||
QueueHandle_t notification_queue_handle; /*!< controller notification queue */
|
||||
LIST_HEAD(mbs_area_descriptors_, mb_descr_entry_s) area_descriptors[MB_PARAM_COUNT]; /*!< register area descriptors */
|
||||
} mb_slave_options_t;
|
||||
|
||||
typedef mb_event_group_t (*iface_check_event_fp)(void *, mb_event_group_t); /*!< Interface method check_event */
|
||||
typedef esp_err_t (*iface_get_param_info_fp)(void *, mb_param_info_t*, uint32_t); /*!< Interface method get_param_info */
|
||||
typedef esp_err_t (*iface_mbs_set_descriptor_fp)(void *, mb_register_area_descriptor_t); /*!< Interface method set_descriptor */
|
||||
|
||||
/**
|
||||
* @brief Request mode for parameter to use in data dictionary
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
mb_base_t *mb_base;
|
||||
mb_slave_options_t opts; /*!< Modbus slave options */
|
||||
bool is_active; /*!< Modbus controller interface is active */
|
||||
|
||||
// Functional pointers to internal static functions of the implementation (public interface methods)
|
||||
iface_create_fp create; /*!< Interface factory method */
|
||||
iface_method_default_fp delete; /*!< Interface method delete */
|
||||
iface_method_default_fp start; /*!< Interface method start */
|
||||
iface_method_default_fp stop; /*!< Interface method start */
|
||||
iface_check_event_fp check_event; /*!< Interface method check_event */
|
||||
iface_get_param_info_fp get_param_info; /*!< Interface method get_param_info */
|
||||
iface_mbs_set_descriptor_fp set_descriptor; /*!< Interface method set_descriptor */
|
||||
} mbs_controller_iface_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
732
modbus/mb_controller/serial/mbc_serial_master.c
Normal file
732
modbus/mb_controller/serial/mbc_serial_master.c
Normal file
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_serial_master.c
|
||||
// Serial master implementation of the Modbus controller
|
||||
|
||||
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
||||
#include "esp_log.h" // for log_write
|
||||
#include <string.h> // for memcpy
|
||||
#include "freertos/FreeRTOS.h" // for task creation
|
||||
#include "freertos/task.h" // for task api access
|
||||
#include "freertos/event_groups.h" // for event groups
|
||||
|
||||
#include "sdkconfig.h" // for KConfig values
|
||||
#include "esp_modbus_common.h" // for common types
|
||||
#include "esp_modbus_master.h" // for public master types
|
||||
#include "mbc_master.h" // for private master types
|
||||
#include "mbc_serial_master.h" // for serial master create function and types
|
||||
|
||||
#include "mb_common.h" // for mb types definition
|
||||
#include "mb_config.h"
|
||||
#include "mb_proto.h"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
/*-----------------------Master mode use these variables----------------------*/
|
||||
|
||||
static const char *TAG = "mbc_serial.master";
|
||||
|
||||
// Modbus event processing task
|
||||
static void mbc_ser_master_task(void *param)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(param);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(param);
|
||||
|
||||
// Main Modbus stack processing cycle
|
||||
for (;;)
|
||||
{
|
||||
// Wait for poll events
|
||||
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
// Check if stack started then poll for data
|
||||
if (status & MB_EVENT_STACK_STARTED)
|
||||
{
|
||||
(void)mbm_iface->mb_base->poll(mbm_iface->mb_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modbus controller stack start function
|
||||
static esp_err_t mbc_serial_master_start(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
mbm_iface->mb_base->descr.parent = ctx;
|
||||
MB_RETURN_ON_FALSE((mbm_opts->mbm_param_descriptor_size >= 1),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb descriptor table size is incorrect.");
|
||||
status = mbm_iface->mb_base->enable(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack start fail, returned (0x%x).", (int)status);
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupSetBits(mbm_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
||||
mbm_iface->is_active = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Modbus controller stack stop function
|
||||
static esp_err_t mbc_serial_master_stop(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
mbm_iface->mb_base->descr.parent = ctx;
|
||||
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupClearBits(mbm_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
status = mbm_iface->mb_base->disable(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack disable fail, returned (0x%x).", (int)status);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack stop event set error.");
|
||||
mbm_iface->is_active = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Modbus controller destroy function
|
||||
static esp_err_t mbc_serial_master_delete(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t mb_error = MB_ENOERR;
|
||||
|
||||
// Check the stack started bit
|
||||
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
pdMS_TO_TICKS(MB_MASTER_TIMEOUT_MS_RESPOND));
|
||||
if (mbm_iface->is_active || (status & MB_EVENT_STACK_STARTED))
|
||||
{
|
||||
ESP_LOGV(TAG, "mb stack is active, try to disable.");
|
||||
MB_RETURN_ON_FALSE((mbc_serial_master_stop(ctx) == ESP_OK),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
|
||||
}
|
||||
|
||||
mbm_iface->is_active = false;
|
||||
vTaskDelete(mbm_opts->task_handle);
|
||||
mbm_opts->task_handle = NULL;
|
||||
vEventGroupDelete(mbm_opts->event_group_handle);
|
||||
mbm_opts->event_group_handle = NULL;
|
||||
mb_error = mbm_iface->mb_base->delete(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack delete failure, returned (0x%x).", (int)mb_error);
|
||||
mbm_iface->mb_base = NULL;
|
||||
free(mbm_iface); // free the memory allocated
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Set Modbus parameter description table
|
||||
static esp_err_t mbc_serial_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((descriptor), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor.");
|
||||
MB_RETURN_ON_FALSE((num_elements >= 1), ESP_ERR_INVALID_ARG, TAG, "mb table size is incorrect.");
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
const mb_parameter_descriptor_t *reg_ptr = descriptor;
|
||||
// Go through all items in the table to check all Modbus registers
|
||||
for (uint16_t counter = 0; counter < (num_elements); counter++, reg_ptr++)
|
||||
{
|
||||
// Below is the code to check consistency of the table format and required fields.
|
||||
MB_RETURN_ON_FALSE((reg_ptr->cid == counter),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb descriptor cid field is incorrect.");
|
||||
MB_RETURN_ON_FALSE((reg_ptr->param_key),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param key is incorrect.");
|
||||
MB_RETURN_ON_FALSE((reg_ptr->mb_size > 0),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param size is incorrect.");
|
||||
}
|
||||
mbm_opts->param_descriptor_table = descriptor;
|
||||
mbm_opts->mbm_param_descriptor_size = num_elements;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Send custom Modbus request defined as mb_param_request_t structure
|
||||
static esp_err_t mbc_serial_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb request structure.");
|
||||
MB_RETURN_ON_FALSE((data_ptr), ESP_ERR_INVALID_ARG, TAG, "mb incorrect data pointer.");
|
||||
|
||||
mb_err_enum_t mb_error = MB_EBUSY;
|
||||
esp_err_t error = ESP_FAIL;
|
||||
|
||||
if (mb_port_event_res_take(mbm_controller_iface->mb_base->port_obj, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS)))
|
||||
{
|
||||
uint8_t mb_slave_addr = request->slave_addr;
|
||||
uint8_t mb_command = request->command;
|
||||
uint16_t mb_offset = request->reg_start;
|
||||
uint16_t mb_size = request->reg_size;
|
||||
|
||||
// Set the buffer for callback function processing of received data
|
||||
mbm_opts->reg_buffer_ptr = (uint8_t *)data_ptr;
|
||||
mbm_opts->reg_buffer_size = mb_size;
|
||||
|
||||
mb_port_event_res_release(mbm_controller_iface->mb_base->port_obj);
|
||||
|
||||
// Calls appropriate request function to send request and waits response
|
||||
switch (mb_command)
|
||||
{
|
||||
|
||||
#if MB_FUNC_READ_COILS_ENABLED
|
||||
case MB_FUNC_READ_COILS:
|
||||
mb_error = mbm_rq_read_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_COIL_ENABLED
|
||||
case MB_FUNC_WRITE_SINGLE_COIL:
|
||||
mb_error = mbm_rq_write_coil(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
*(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
|
||||
case MB_FUNC_WRITE_MULTIPLE_COILS:
|
||||
mb_error = mbm_rq_write_multi_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size, (uint8_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
|
||||
case MB_FUNC_READ_DISCRETE_INPUTS:
|
||||
mb_error = mbm_rq_read_discrete_inputs(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_HOLDING_ENABLED
|
||||
case MB_FUNC_READ_HOLDING_REGISTER:
|
||||
mb_error = mbm_rq_read_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_HOLDING_ENABLED
|
||||
case MB_FUNC_WRITE_REGISTER:
|
||||
mb_error = mbm_rq_write_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
*(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
|
||||
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
|
||||
mb_error = mbm_rq_write_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr,
|
||||
(uint16_t)mb_offset, (uint16_t)mb_size,
|
||||
(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READWRITE_HOLDING_ENABLED
|
||||
case MB_FUNC_READWRITE_MULTIPLE_REGISTERS:
|
||||
mb_error = mbm_rq_rw_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size, (uint16_t *)data_ptr,
|
||||
(uint16_t)mb_offset, (uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_INPUT_ENABLED
|
||||
case MB_FUNC_READ_INPUT_REGISTER:
|
||||
mb_error = mbm_rq_read_inp_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
|
||||
__FUNCTION__, mb_command);
|
||||
mb_error = MB_ENOREG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate the Modbus errors to higher level
|
||||
switch (mb_error)
|
||||
{
|
||||
case MB_ENOERR:
|
||||
error = ESP_OK;
|
||||
break;
|
||||
|
||||
case MB_ENOREG:
|
||||
error = ESP_ERR_NOT_SUPPORTED; // Invalid register request
|
||||
break;
|
||||
|
||||
case MB_ETIMEDOUT:
|
||||
error = ESP_ERR_TIMEOUT; // Slave did not send response
|
||||
break;
|
||||
|
||||
case MB_EILLFUNC:
|
||||
case MB_ERECVDATA:
|
||||
error = ESP_ERR_INVALID_RESPONSE; // Invalid response from slave
|
||||
break;
|
||||
|
||||
case MB_EBUSY:
|
||||
error = ESP_ERR_INVALID_STATE; // Master is busy (previous request is pending)
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (uint16_t)mb_error);
|
||||
error = ESP_FAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static esp_err_t mbc_serial_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t **param_buffer)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
|
||||
MB_RETURN_ON_FALSE((param_buffer),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect data buffer pointer.");
|
||||
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor table or not set.");
|
||||
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size),
|
||||
ESP_ERR_NOT_FOUND, TAG, "mb incorrect cid of characteristic.");
|
||||
|
||||
// It is assumed that characteristics cid increased in the table
|
||||
const mb_parameter_descriptor_t *reg_info = &mbm_opts->param_descriptor_table[cid];
|
||||
|
||||
MB_RETURN_ON_FALSE((reg_info->param_key),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect characteristic key.");
|
||||
*param_buffer = reg_info;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Helper function to get modbus command for each type of Modbus register area
|
||||
static uint8_t mbc_serial_master_get_command(mb_param_type_t param_type, mb_param_mode_t mode)
|
||||
{
|
||||
uint8_t command = 0;
|
||||
switch (param_type)
|
||||
{
|
||||
case MB_PARAM_HOLDING:
|
||||
command = (mode == MB_PARAM_WRITE) ? MB_FUNC_WRITE_MULTIPLE_REGISTERS : MB_FUNC_READ_HOLDING_REGISTER;
|
||||
break;
|
||||
case MB_PARAM_INPUT:
|
||||
command = MB_FUNC_READ_INPUT_REGISTER;
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
command = (mode == MB_PARAM_WRITE) ? MB_FUNC_WRITE_MULTIPLE_COILS : MB_FUNC_READ_COILS;
|
||||
break;
|
||||
case MB_PARAM_DISCRETE:
|
||||
if (mode != MB_PARAM_WRITE)
|
||||
{
|
||||
command = MB_FUNC_READ_DISCRETE_INPUTS;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "%s: Incorrect mode (%u)",
|
||||
__FUNCTION__, (unsigned)mode);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect param type (%u)",
|
||||
__FUNCTION__, (unsigned)param_type);
|
||||
break;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
// Helper to search parameter by name in the parameter description table
|
||||
// and fills Modbus request fields accordingly
|
||||
static esp_err_t mbc_serial_master_set_request(void *ctx, uint16_t cid, mb_param_mode_t mode,
|
||||
mb_param_request_t *request,
|
||||
mb_parameter_descriptor_t *reg_data)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
esp_err_t error = ESP_ERR_NOT_FOUND;
|
||||
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb incorrect request parameter.");
|
||||
MB_RETURN_ON_FALSE((mode <= MB_PARAM_WRITE), ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode.");
|
||||
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size), ESP_ERR_INVALID_ARG, TAG, "mb incorrect cid parameter.");
|
||||
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table), ESP_ERR_INVALID_ARG, TAG, "mb data dictionary is incorrect.");
|
||||
const mb_parameter_descriptor_t *reg_ptr = mbm_opts->param_descriptor_table;
|
||||
reg_ptr += cid;
|
||||
if (reg_ptr->cid == cid)
|
||||
{
|
||||
request->slave_addr = reg_ptr->mb_slave_addr;
|
||||
request->reg_start = reg_ptr->mb_reg_start;
|
||||
request->reg_size = reg_ptr->mb_size;
|
||||
request->command = mbc_serial_master_get_command(reg_ptr->mb_param_type, mode);
|
||||
MB_RETURN_ON_FALSE((request->command > 0), ESP_ERR_INVALID_ARG, TAG, "mb incorrect command or parameter type.");
|
||||
if (reg_data)
|
||||
{
|
||||
*reg_data = *reg_ptr; // Set the cid registered parameter data
|
||||
}
|
||||
error = ESP_OK;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Get parameter data for corresponding characteristic
|
||||
static esp_err_t mbc_serial_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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.");
|
||||
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
||||
mb_param_request_t request ;
|
||||
mb_parameter_descriptor_t reg_info = { 0 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
// alloc buffer to store parameter data
|
||||
pdata = calloc(1, (reg_info.mb_size << 1));
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
error = mbc_serial_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
||||
if (value) {
|
||||
error = mbc_master_set_param_data((void *)value, (void *)pdata,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
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));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// 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);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
// Get parameter data for corresponding characteristic
|
||||
static esp_err_t mbc_serial_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
|
||||
uint8_t *value_ptr, uint8_t *type)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
||||
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
||||
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
||||
mb_param_request_t request;
|
||||
mb_parameter_descriptor_t reg_info = {0};
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid))
|
||||
{
|
||||
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
||||
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
||||
}
|
||||
request.slave_addr = uid; // override the UID
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
// alloc buffer to store parameter data
|
||||
pdata = calloc(1, (reg_info.mb_size << 1));
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Send request to read characteristic data
|
||||
error = mbc_serial_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK)
|
||||
{
|
||||
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
||||
if (value_ptr) {
|
||||
error = mbc_master_set_param_data((void *)value_ptr, (void *)pdata,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// 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);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Set parameter value for characteristic selected by cid
|
||||
static esp_err_t mbc_serial_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
||||
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
||||
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
||||
mb_param_request_t request ;
|
||||
mb_parameter_descriptor_t reg_info = { 0 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Transfer value of characteristic into parameter buffer
|
||||
error = mbc_master_set_param_data((void *)pdata, (void *)value,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
free(pdata);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Send request to write characteristic data
|
||||
error = mbc_serial_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// Set the type of parameter found in the table
|
||||
*type = reg_info.param_type;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
||||
__FUNCTION__, (unsigned)reg_info.cid);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Set parameter value for characteristic selected by name and cid
|
||||
static esp_err_t mbc_serial_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
|
||||
uint8_t *value_ptr, uint8_t *type)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
||||
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
||||
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
||||
mb_param_request_t request;
|
||||
mb_parameter_descriptor_t reg_info = {0};
|
||||
uint8_t *pdata = NULL;
|
||||
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid))
|
||||
{
|
||||
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
||||
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
||||
}
|
||||
request.slave_addr = uid; // override the UID
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Transfer value of characteristic into parameter buffer
|
||||
error = mbc_master_set_param_data((void *)pdata, (void *)value_ptr,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
free(pdata);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Send request to write characteristic data
|
||||
error = mbc_serial_master_send_request(ctx, &request, value_ptr);
|
||||
if (error == ESP_OK)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// Set the type of parameter found in the table
|
||||
*type = reg_info.param_type;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
||||
__FUNCTION__, (unsigned)reg_info.cid);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static void mbc_serial_master_iface_free(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = (mbm_controller_iface_t *)(ctx);
|
||||
if (mbm_iface)
|
||||
{
|
||||
if (mbm_iface->opts.task_handle)
|
||||
{
|
||||
vTaskDelete(mbm_iface->opts.task_handle);
|
||||
mbm_iface->opts.task_handle = NULL;
|
||||
}
|
||||
if (mbm_iface->opts.event_group_handle)
|
||||
{
|
||||
vEventGroupDelete(mbm_iface->opts.event_group_handle);
|
||||
mbm_iface->opts.event_group_handle = NULL;
|
||||
}
|
||||
free(mbm_iface); // free the memory allocated for interface
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t mbc_serial_master_controller_create(void **ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack init interface fail.");
|
||||
mbm_controller_iface_t *mbm_controller_iface = NULL;
|
||||
|
||||
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
||||
BaseType_t status = 0;
|
||||
|
||||
// Allocate space for controller
|
||||
mbm_controller_iface = malloc(sizeof(mbm_controller_iface_t));
|
||||
MB_GOTO_ON_FALSE((mbm_controller_iface), ESP_ERR_INVALID_STATE, error,
|
||||
TAG, "mb stack memory allocation fail.");
|
||||
|
||||
// Initialize interface properties
|
||||
mb_master_options_t *mbm_opts = &mbm_controller_iface->opts;
|
||||
|
||||
// Initialization of active context of the modbus controller
|
||||
mbm_opts->event_group_handle = xEventGroupCreate();
|
||||
MB_GOTO_ON_FALSE((mbm_opts->event_group_handle), ESP_ERR_INVALID_STATE, error, TAG, "mb event group error.");
|
||||
// Create modbus controller task
|
||||
status = xTaskCreatePinnedToCore((void *)&mbc_ser_master_task,
|
||||
"mbc_ser_master",
|
||||
MB_CONTROLLER_STACK_SIZE,
|
||||
mbm_controller_iface,
|
||||
MB_CONTROLLER_PRIORITY,
|
||||
&mbm_opts->task_handle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mb controller task creation error");
|
||||
MB_MASTER_ASSERT(mbm_opts->task_handle); // The task is created but handle is incorrect
|
||||
|
||||
// Initialize public interface methods of the interface
|
||||
mbm_controller_iface->create = mbc_serial_master_create;
|
||||
mbm_controller_iface->delete = mbc_serial_master_delete;
|
||||
mbm_controller_iface->start = mbc_serial_master_start;
|
||||
mbm_controller_iface->stop = mbc_serial_master_stop;
|
||||
mbm_controller_iface->get_cid_info = mbc_serial_master_get_cid_info;
|
||||
mbm_controller_iface->get_parameter = mbc_serial_master_get_parameter;
|
||||
mbm_controller_iface->get_parameter_with = mbc_serial_master_get_parameter_with;
|
||||
mbm_controller_iface->send_request = mbc_serial_master_send_request;
|
||||
mbm_controller_iface->set_descriptor = mbc_serial_master_set_descriptor;
|
||||
mbm_controller_iface->set_parameter = mbc_serial_master_set_parameter;
|
||||
mbm_controller_iface->set_parameter_with = mbc_serial_master_set_parameter_with;
|
||||
mbm_controller_iface->mb_base = NULL;
|
||||
*ctx = mbm_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
mbc_serial_master_iface_free((void *)mbm_controller_iface);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialization of resources for Modbus serial master controller
|
||||
esp_err_t mbc_serial_master_create(mb_communication_info_t *config, void **ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_controller_iface = NULL;
|
||||
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
MB_RETURN_ON_FALSE((!*ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack is not destroyed?");
|
||||
|
||||
mb_serial_opts_t *pcomm_info = &config->ser_opts;
|
||||
|
||||
// Check communication options
|
||||
MB_RETURN_ON_FALSE(((pcomm_info->mode == MB_RTU) || (pcomm_info->mode == MB_ASCII)),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode = (%u).", (unsigned)pcomm_info->mode);
|
||||
MB_RETURN_ON_FALSE((pcomm_info->port <= UART_NUM_MAX), ESP_ERR_INVALID_ARG, TAG,
|
||||
"mb wrong port to set = (%u).", (unsigned)pcomm_info->port);
|
||||
MB_RETURN_ON_FALSE((pcomm_info->parity <= UART_PARITY_ODD), ESP_ERR_INVALID_ARG, TAG,
|
||||
"mb wrong parity option = (%u).", (unsigned)pcomm_info->parity);
|
||||
|
||||
esp_err_t ret = mbc_serial_master_controller_create((void *)&mbm_controller_iface);
|
||||
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG, "mbc create returns (0x%x).", (int)ret);
|
||||
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
|
||||
mbm_opts->comm_opts = *config;
|
||||
mbm_opts->port_type = MB_PORT_SERIAL_MASTER;
|
||||
|
||||
// Keep the response time setting
|
||||
if (!pcomm_info->response_tout_ms)
|
||||
{
|
||||
mbm_opts->comm_opts.ser_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
|
||||
}
|
||||
|
||||
// Initialize Modbus stack using mbcontroller parameters
|
||||
mb_err_enum_t err = MB_EILLSTATE;
|
||||
void *pinst = (void *)mbm_controller_iface;
|
||||
|
||||
if (pcomm_info->mode == MB_RTU)
|
||||
{
|
||||
err = mbm_rtu_create(pcomm_info, &pinst);
|
||||
}
|
||||
else if (pcomm_info->mode == MB_ASCII)
|
||||
{
|
||||
err = mbm_ascii_create(pcomm_info, &pinst);
|
||||
}
|
||||
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mb object create returns (0x%x).", (int)err);
|
||||
mbm_controller_iface->mb_base = (mb_base_t *)pinst;
|
||||
|
||||
const mb_rw_callbacks_t rw_cbs = {
|
||||
.reg_input_cb = mbc_reg_input_master_cb,
|
||||
.reg_holding_cb = mbc_reg_holding_master_cb,
|
||||
.reg_coils_cb = mbc_reg_coils_master_cb,
|
||||
.reg_discrete_cb = mbc_reg_discrete_master_cb
|
||||
};
|
||||
|
||||
mbm_controller_iface->mb_base->rw_cbs = rw_cbs;
|
||||
mbm_controller_iface->is_active = false;
|
||||
*ctx = mbm_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbm_controller_iface)
|
||||
{
|
||||
if (mbm_controller_iface->mb_base)
|
||||
{
|
||||
mbm_controller_iface->mb_base->delete(mbm_controller_iface->mb_base);
|
||||
mbm_controller_iface->mb_base = NULL;
|
||||
}
|
||||
mbc_serial_master_iface_free((void *)mbm_controller_iface);
|
||||
*ctx = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
39
modbus/mb_controller/serial/mbc_serial_master.h
Normal file
39
modbus/mb_controller/serial/mbc_serial_master.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_serial_master.h Modbus controller serial master implementation header file
|
||||
|
||||
#pragma once
|
||||
|
||||
#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_err.h" // for esp_err_t
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus controller and stack
|
||||
*
|
||||
* @param[out] ctx - pointer to pointer of interface structure
|
||||
* @param[in] config - pointer to configuration structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
*/
|
||||
esp_err_t mbc_serial_master_create(mb_communication_info_t *config, void **ctx);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
293
modbus/mb_controller/serial/mbc_serial_slave.c
Normal file
293
modbus/mb_controller/serial/mbc_serial_slave.c
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
// mbc_serial_slave.c
|
||||
// Implementation of the Modbus controller serial slave
|
||||
|
||||
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
||||
#include "esp_log.h" // for log_write
|
||||
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "esp_modbus_slave.h" // for public slave interface types
|
||||
#include "mbc_slave.h" // for private slave interface types
|
||||
#include "mbc_serial_slave.h" // for serial slave implementation definitions
|
||||
|
||||
#include "mb_common.h" // for mb object types definition
|
||||
|
||||
#include "sdkconfig.h" // for KConfig values
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
static const char *TAG = "mbc_serial.slave";
|
||||
|
||||
// Modbus task function
|
||||
static void mbc_ser_slave_task(void *param)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(param);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(param);
|
||||
|
||||
// Main Modbus stack processing cycle
|
||||
for (;;)
|
||||
{
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE, // do not clear bits
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
// Check if stack started then poll for data
|
||||
if (status & MB_EVENT_STACK_STARTED)
|
||||
{
|
||||
(void)mbs_iface->mb_base->poll(mbs_iface->mb_base);
|
||||
}
|
||||
// esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Start Modbus controller start function
|
||||
static esp_err_t mbc_serial_slave_start(void *ctx)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
|
||||
status = mbs_iface->mb_base->enable(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack enable fail, returned (0x%x).", (int)status);
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupSetBits(mbs_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
||||
mbs_iface->mb_base->descr.parent = ctx;
|
||||
mbs_iface->is_active = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Start Modbus controller stop function
|
||||
static esp_err_t mbc_serial_slave_stop(void *ctx)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
// Clear the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupClearBits(mbs_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
||||
|
||||
status = mbs_iface->mb_base->disable(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack disable fail, returned (0x%x).", (int)status);
|
||||
mbs_iface->mb_base->descr.parent = NULL;
|
||||
mbs_iface->is_active = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Blocking function to get event on parameter group change for application task
|
||||
static mb_event_group_t mbc_serial_slave_check_event(void *ctx, mb_event_group_t group)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
MB_SLAVE_ASSERT(mbs_opts->event_group_handle);
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle, (BaseType_t)group,
|
||||
pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
return (mb_event_group_t)status;
|
||||
}
|
||||
|
||||
// Function to get notification about parameter change from application task
|
||||
static esp_err_t mbc_serial_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
esp_err_t err = ESP_ERR_TIMEOUT;
|
||||
MB_RETURN_ON_FALSE((mbs_opts->notification_queue_handle),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb queue handle is invalid.");
|
||||
MB_RETURN_ON_FALSE((reg_info), ESP_ERR_INVALID_ARG, TAG, "mb register information is invalid.");
|
||||
BaseType_t status = xQueueReceive(mbs_opts->notification_queue_handle,
|
||||
reg_info, pdMS_TO_TICKS(timeout));
|
||||
if (status == pdTRUE)
|
||||
{
|
||||
err = ESP_OK;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// Modbus controller delete function
|
||||
static esp_err_t mbc_serial_slave_delete(void *ctx)
|
||||
{
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mb_err_enum_t mb_error = MB_ENOERR;
|
||||
|
||||
// Check the stack started bit
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
MB_CONTROLLER_NOTIFY_TIMEOUT);
|
||||
if (mbs_iface->is_active || (status & MB_EVENT_STACK_STARTED))
|
||||
{
|
||||
ESP_LOGV(TAG, "mb stack is active, try to disable.");
|
||||
if (mbc_serial_slave_stop(ctx) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mb stack stop failure.");
|
||||
}
|
||||
}
|
||||
|
||||
mbs_iface->is_active = false;
|
||||
vTaskDelete(mbs_opts->task_handle);
|
||||
vEventGroupDelete(mbs_opts->event_group_handle);
|
||||
vQueueDelete(mbs_opts->notification_queue_handle);
|
||||
mbs_opts->notification_queue_handle = NULL;
|
||||
mbs_opts->event_group_handle = NULL;
|
||||
mbs_opts->task_handle = NULL;
|
||||
mb_error = mbs_iface->mb_base->delete(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack close failure returned (0x%x).", (int)mb_error);
|
||||
// free the controller will be performed in common slave object
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mbc_serial_slave_iface_free(void *ctx)
|
||||
{
|
||||
mbs_controller_iface_t *mbs_iface = (mbs_controller_iface_t *)(ctx);
|
||||
if (mbs_iface)
|
||||
{
|
||||
if (mbs_iface->opts.task_handle)
|
||||
{
|
||||
vTaskDelete(mbs_iface->opts.task_handle);
|
||||
mbs_iface->opts.task_handle = NULL;
|
||||
}
|
||||
if (mbs_iface->opts.event_group_handle)
|
||||
{
|
||||
vEventGroupDelete(mbs_iface->opts.event_group_handle);
|
||||
mbs_iface->opts.event_group_handle = NULL;
|
||||
}
|
||||
if (mbs_iface->opts.notification_queue_handle)
|
||||
{
|
||||
vQueueDelete(mbs_iface->opts.notification_queue_handle);
|
||||
}
|
||||
free(mbs_iface); // free the memory allocated for interface
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t mbc_serial_slave_controller_create(void **ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
||||
mbs_controller_iface_t *mbs_controller_iface = malloc(sizeof(mbs_controller_iface_t));
|
||||
MB_GOTO_ON_FALSE((mbs_controller_iface), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb stack memory allocation fail.");
|
||||
|
||||
mb_slave_options_t *mbs_opts = &mbs_controller_iface->opts;
|
||||
mbs_opts->port_type = MB_PORT_SERIAL_SLAVE; // set interface port type
|
||||
|
||||
// Initialization of active context of the Modbus controller
|
||||
BaseType_t status = 0;
|
||||
// Parameter change notification queue
|
||||
mbs_opts->event_group_handle = xEventGroupCreate();
|
||||
MB_GOTO_ON_FALSE((mbs_opts->event_group_handle), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb event group error.");
|
||||
// Parameter change notification queue
|
||||
mbs_opts->notification_queue_handle = xQueueCreate(MB_CONTROLLER_NOTIFY_QUEUE_SIZE, sizeof(mb_param_info_t));
|
||||
MB_GOTO_ON_FALSE((mbs_opts->notification_queue_handle), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb notify queue creation error.");
|
||||
// Create Modbus controller task
|
||||
status = xTaskCreatePinnedToCore((void *)&mbc_ser_slave_task,
|
||||
"mbc_ser_slave",
|
||||
MB_CONTROLLER_STACK_SIZE,
|
||||
mbs_controller_iface,
|
||||
MB_CONTROLLER_PRIORITY,
|
||||
&mbs_opts->task_handle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mb controller task creation error");
|
||||
MB_SLAVE_ASSERT(mbs_opts->task_handle); // The task is created but handle is incorrect
|
||||
|
||||
// Initialize interface function pointers
|
||||
mbs_controller_iface->create = mbc_serial_slave_create;
|
||||
mbs_controller_iface->delete = mbc_serial_slave_delete;
|
||||
mbs_controller_iface->check_event = mbc_serial_slave_check_event;
|
||||
mbs_controller_iface->get_param_info = mbc_serial_slave_get_param_info;
|
||||
mbs_controller_iface->set_descriptor = NULL; // Use common set descriptor function
|
||||
mbs_controller_iface->start = mbc_serial_slave_start;
|
||||
mbs_controller_iface->stop = mbc_serial_slave_stop;
|
||||
mbs_controller_iface->mb_base = NULL;
|
||||
*ctx = mbs_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
mbc_serial_slave_iface_free((void *)mbs_controller_iface);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialization of Modbus controller
|
||||
esp_err_t mbc_serial_slave_create(mb_communication_info_t *config, void **ctx)
|
||||
{
|
||||
mbs_controller_iface_t *mbs_controller_iface = NULL;
|
||||
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
MB_RETURN_ON_FALSE((!*ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack is not destroyed?");
|
||||
mb_serial_opts_t *pcomm_info = &config->ser_opts;
|
||||
|
||||
// Check communication options
|
||||
MB_RETURN_ON_FALSE(((pcomm_info->mode == MB_RTU) || (pcomm_info->mode == MB_ASCII)),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode = (%u).",
|
||||
(unsigned)pcomm_info->mode);
|
||||
MB_RETURN_ON_FALSE((pcomm_info->port <= UART_NUM_MAX), ESP_ERR_INVALID_ARG, TAG,
|
||||
"mb wrong port to set = (%u).", (unsigned)pcomm_info->port);
|
||||
MB_RETURN_ON_FALSE((pcomm_info->parity <= UART_PARITY_ODD), ESP_ERR_INVALID_ARG, TAG,
|
||||
"mb wrong parity option = (%u).", (unsigned)pcomm_info->parity);
|
||||
MB_RETURN_ON_FALSE((pcomm_info->uid <= MB_ADDRESS_MAX),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb wrong slave address = (0x%u).",
|
||||
(unsigned)pcomm_info->uid);
|
||||
|
||||
esp_err_t ret = mbc_serial_slave_controller_create((void *)&mbs_controller_iface);
|
||||
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbc create returns (0x%x).", (int)ret);
|
||||
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(mbs_controller_iface);
|
||||
mbs_opts->port_type = MB_PORT_SERIAL_SLAVE;
|
||||
mbs_opts->comm_opts = *config;
|
||||
mb_err_enum_t err = MB_ENOERR;
|
||||
void *pinst = (void *)mbs_controller_iface;
|
||||
|
||||
// Initialize Modbus stack using mbcontroller parameters
|
||||
if (pcomm_info->mode == MB_RTU)
|
||||
{
|
||||
err = mbs_rtu_create(pcomm_info, &pinst);
|
||||
}
|
||||
else if (pcomm_info->mode == MB_ASCII)
|
||||
{
|
||||
err = mbs_ascii_create(pcomm_info, &pinst);
|
||||
}
|
||||
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbs create returns (0x%x).", (int)err);
|
||||
mbs_controller_iface->mb_base = (mb_base_t *)pinst;
|
||||
|
||||
// Configure Modbus read/write callbacks for the base modbus object
|
||||
const mb_rw_callbacks_t rw_cbs = {
|
||||
.reg_input_cb = mbc_reg_input_slave_cb,
|
||||
.reg_holding_cb = mbc_reg_holding_slave_cb,
|
||||
.reg_coils_cb = mbc_reg_coils_slave_cb,
|
||||
.reg_discrete_cb = mbc_reg_discrete_slave_cb
|
||||
};
|
||||
|
||||
mbs_controller_iface->mb_base->rw_cbs = rw_cbs;
|
||||
mbs_controller_iface->is_active = false;
|
||||
*ctx = (void *)mbs_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbs_controller_iface) {
|
||||
if (mbs_controller_iface->mb_base) {
|
||||
mbs_controller_iface->mb_base->delete (mbs_controller_iface->mb_base);
|
||||
mbs_controller_iface->mb_base = NULL;
|
||||
}
|
||||
mbc_serial_slave_iface_free((void *)mbs_controller_iface);
|
||||
*ctx = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
41
modbus/mb_controller/serial/mbc_serial_slave.h
Normal file
41
modbus/mb_controller/serial/mbc_serial_slave.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_serial_slave.h Modbus controller serial slave implementation header file
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
#define MB_CONTROLLER_NOTIFY_QUEUE_SIZE (CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE) // Number of messages in parameter notification queue
|
||||
#define MB_CONTROLLER_NOTIFY_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT)) // notification timeout
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
/*
|
||||
* @brief Initialize Modbus controller and stack
|
||||
*
|
||||
* @param[out] ctx - pointer to pointer of interface structure
|
||||
* @param[in] config - pointer to configuration structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
*/
|
||||
esp_err_t mbc_serial_slave_create(mb_communication_info_t *config, void **ctx);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
768
modbus/mb_controller/tcp/mbc_tcp_master.c
Normal file
768
modbus/mb_controller/tcp/mbc_tcp_master.c
Normal file
@@ -0,0 +1,768 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_tcp_master.c
|
||||
// TCP master implementation of the Modbus controller
|
||||
|
||||
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
||||
#include "esp_log.h" // for log_write
|
||||
#include <string.h> // for memcpy
|
||||
#include <sys/queue.h> // for list
|
||||
#include "freertos/FreeRTOS.h" // for task creation and queue access
|
||||
#include "freertos/task.h" // for task api access
|
||||
#include "freertos/event_groups.h" // for event groups
|
||||
#include "freertos/queue.h" // for queue api access
|
||||
|
||||
#include "sdkconfig.h" // for KConfig values
|
||||
#include "esp_modbus_common.h" // for common types
|
||||
#include "esp_modbus_master.h" // for public master types
|
||||
#include "mbc_master.h" // for private master types
|
||||
#include "mbc_tcp_master.h" // for tcp master create function and types
|
||||
#include "port_tcp_common.h"
|
||||
#include "port_tcp_master.h"
|
||||
|
||||
#include "mb_common.h" // for mb types definition
|
||||
#include "mb_config.h"
|
||||
#include "mb_proto.h"
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#if MB_MASTER_TCP_ENABLED
|
||||
|
||||
/*-----------------------Master mode use these variables----------------------*/
|
||||
static const char *TAG = "mbc_tcp.master";
|
||||
|
||||
#define MB_TCP_CONNECTION_TOUT pdMS_TO_TICKS(CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000)
|
||||
|
||||
//typedef enum _mb_sock_state mb_sock_state_t;
|
||||
|
||||
// Modbus event processing task
|
||||
static void modbus_tcp_master_task(void *param)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(param);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(param);
|
||||
|
||||
// Main Modbus stack processing cycle
|
||||
for (;;) {
|
||||
// Wait for poll events
|
||||
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
// Check if stack started then poll for data
|
||||
if (status & MB_EVENT_STACK_STARTED) {
|
||||
(void)mbm_iface->mb_base->poll(mbm_iface->mb_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mbc_tcp_master_conn_done_cb(void *ctx)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
|
||||
ESP_LOGI(TAG, "mb controller connection done.");
|
||||
EventBits_t flag = xEventGroupSetBits(mbm_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_CONNECTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_CONNECTED),
|
||||
;, TAG, "mb stack connected event set error.");
|
||||
}
|
||||
|
||||
// Modbus controller stack start function
|
||||
static esp_err_t mbc_tcp_master_start(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
mbm_iface->mb_base->descr.parent = ctx;
|
||||
|
||||
MB_RETURN_ON_FALSE((mbm_opts->mbm_param_descriptor_size >= 1),
|
||||
ESP_ERR_INVALID_ARG, TAG,"mb descriptor table size is incorrect.");
|
||||
|
||||
status = mbm_iface->mb_base->enable(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack start fail, returned (0x%x).", (uint16_t)status);
|
||||
// Wait the connection esteblished before start polling according to the option
|
||||
if (!mbm_opts->comm_opts.tcp_opts.start_disconnected) {
|
||||
BaseType_t start = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_CONNECTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
MB_TCP_CONNECTION_TOUT);
|
||||
MB_RETURN_ON_FALSE((start), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack could not connect to slaves for %u seconds.",
|
||||
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC);
|
||||
}
|
||||
mbm_iface->is_active = true;
|
||||
|
||||
xEventGroupSetBits(mbm_opts->event_group_handle, (EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Modbus controller stack stop function
|
||||
static esp_err_t mbc_tcp_master_stop(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
mbm_iface->mb_base->descr.parent = ctx;
|
||||
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupClearBits(mbm_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack stop event set error.");
|
||||
|
||||
status = mbm_iface->mb_base->disable(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack disable fail, returned (0x%x).", (uint16_t)status);
|
||||
mbm_iface->is_active = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Set Modbus parameter description table
|
||||
static esp_err_t mbc_tcp_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((descriptor), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor.");
|
||||
MB_RETURN_ON_FALSE((num_elements >= 1), ESP_ERR_INVALID_ARG, TAG, "mb table size is incorrect.");
|
||||
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
|
||||
const char **comm_ip_table = (const char **)mbm_opts->comm_opts.tcp_opts.ip_addr_table;
|
||||
MB_RETURN_ON_FALSE((comm_ip_table), ESP_ERR_INVALID_ARG, TAG, "mb ip table address is incorrect.");
|
||||
|
||||
const mb_parameter_descriptor_t *reg_ptr = descriptor;
|
||||
mb_uid_info_t *paddr_info = NULL;
|
||||
|
||||
// Go through all items in the table to check all Modbus registers
|
||||
for (int idx = 0; idx < (num_elements); idx++, reg_ptr++) {
|
||||
// Check consistency of the table format and required fields.
|
||||
MB_RETURN_ON_FALSE((reg_ptr->cid == idx), ESP_ERR_INVALID_ARG, TAG, "mb descriptor cid field is incorrect.");
|
||||
MB_RETURN_ON_FALSE((reg_ptr->param_key), ESP_ERR_INVALID_ARG, TAG, "mb descriptor param key is incorrect.");
|
||||
MB_RETURN_ON_FALSE((reg_ptr->mb_size > 0), ESP_ERR_INVALID_ARG, TAG, "mb descriptor param size is incorrect.");
|
||||
|
||||
if (reg_ptr->mb_slave_addr == MB_SLAVE_ADDR_PLACEHOLDER) {
|
||||
continue; // skip not defined uid in the data dictionary
|
||||
}
|
||||
|
||||
// Is the slave with the UID already in the list?
|
||||
paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj, reg_ptr->mb_slave_addr, MB_SOCK_STATE_OPENED);
|
||||
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_INVALID_ARG, TAG,
|
||||
"mb missing IP address configuration for cid #%u, uid=%d.", (unsigned)reg_ptr->cid, (int)reg_ptr->mb_slave_addr);
|
||||
ESP_LOGI(TAG, "mb found config for cid #%d, uid=%d.", (int)reg_ptr->cid, (int)reg_ptr->mb_slave_addr);
|
||||
}
|
||||
mbm_opts->param_descriptor_table = descriptor;
|
||||
mbm_opts->mbm_param_descriptor_size = num_elements;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Send custom Modbus request defined as mb_param_request_t structure
|
||||
static esp_err_t mbc_tcp_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb request structure.");
|
||||
MB_RETURN_ON_FALSE((data_ptr), ESP_ERR_INVALID_ARG, TAG, "mb incorrect data pointer.");
|
||||
|
||||
mb_err_enum_t mb_error = MB_EBUSY;
|
||||
esp_err_t error = ESP_FAIL;
|
||||
|
||||
if (mb_port_event_res_take(mbm_controller_iface->mb_base->port_obj, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS))) {
|
||||
uint8_t mb_slave_addr = request->slave_addr;
|
||||
uint8_t mb_command = request->command;
|
||||
uint16_t mb_offset = request->reg_start;
|
||||
uint16_t mb_size = request->reg_size;
|
||||
|
||||
// Set the buffer for callback function processing of received data
|
||||
mbm_opts->reg_buffer_ptr = (uint8_t *)data_ptr;
|
||||
mbm_opts->reg_buffer_size = mb_size;
|
||||
|
||||
mb_port_event_res_release(mbm_controller_iface->mb_base->port_obj);
|
||||
|
||||
// Calls appropriate request function to send request and waits response
|
||||
switch(mb_command) {
|
||||
#if MB_FUNC_READ_COILS_ENABLED
|
||||
case MB_FUNC_READ_COILS:
|
||||
mb_error = mbm_rq_read_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size ,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_COIL_ENABLED
|
||||
case MB_FUNC_WRITE_SINGLE_COIL:
|
||||
mb_error = mbm_rq_write_coil(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
*(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
|
||||
case MB_FUNC_WRITE_MULTIPLE_COILS:
|
||||
mb_error = mbm_rq_write_multi_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size, (uint8_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
|
||||
case MB_FUNC_READ_DISCRETE_INPUTS:
|
||||
mb_error = mbm_rq_read_discrete_inputs(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_HOLDING_ENABLED
|
||||
case MB_FUNC_READ_HOLDING_REGISTER:
|
||||
mb_error = mbm_rq_read_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_HOLDING_ENABLED
|
||||
case MB_FUNC_WRITE_REGISTER:
|
||||
mb_error = mbm_rq_write_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
*(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
|
||||
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
|
||||
mb_error = mbm_rq_write_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr,
|
||||
(uint16_t)mb_offset, (uint16_t)mb_size,
|
||||
(uint16_t *)data_ptr,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READWRITE_HOLDING_ENABLED
|
||||
case MB_FUNC_READWRITE_MULTIPLE_REGISTERS:
|
||||
mb_error = mbm_rq_rw_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size, (uint16_t *)data_ptr,
|
||||
(uint16_t)mb_offset, (uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_INPUT_ENABLED
|
||||
case MB_FUNC_READ_INPUT_REGISTER:
|
||||
mb_error = mbm_rq_read_inp_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
|
||||
(uint16_t)mb_size,
|
||||
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
|
||||
__FUNCTION__, (unsigned)mb_command);
|
||||
mb_error = MB_ENOREG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate the Modbus errors to higher level
|
||||
switch(mb_error) {
|
||||
case MB_ENOERR:
|
||||
error = ESP_OK;
|
||||
break;
|
||||
|
||||
case MB_ENOREG:
|
||||
error = ESP_ERR_NOT_SUPPORTED; // Invalid register request
|
||||
break;
|
||||
|
||||
case MB_ETIMEDOUT:
|
||||
error = ESP_ERR_TIMEOUT; // Slave did not send response
|
||||
break;
|
||||
|
||||
case MB_EILLFUNC:
|
||||
case MB_ERECVDATA:
|
||||
error = ESP_ERR_INVALID_RESPONSE; // Invalid response from slave
|
||||
break;
|
||||
|
||||
case MB_EBUSY:
|
||||
error = ESP_ERR_INVALID_STATE; // Master is busy (previous request is pending)
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (int)mb_error);
|
||||
error = ESP_FAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static esp_err_t mbc_tcp_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t** param_buffer)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
|
||||
MB_RETURN_ON_FALSE((param_buffer),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect data buffer pointer.");
|
||||
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor table or not set.");
|
||||
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size),
|
||||
ESP_ERR_NOT_FOUND, TAG, "mb incorrect cid of characteristic.");
|
||||
|
||||
// It is assumed that characteristics cid increased in the table
|
||||
const mb_parameter_descriptor_t *reg_info = &mbm_opts->param_descriptor_table[cid];
|
||||
|
||||
MB_RETURN_ON_FALSE((reg_info->param_key),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb incorrect characteristic key.");
|
||||
*param_buffer = reg_info;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Helper function to get modbus command for each type of Modbus register area
|
||||
static uint8_t mbc_tcp_master_get_command(mb_param_type_t param_type, mb_param_mode_t mode)
|
||||
{
|
||||
uint8_t command = 0;
|
||||
switch(param_type){ // Check commands
|
||||
case MB_PARAM_HOLDING:
|
||||
command = (mode == MB_PARAM_WRITE) ?
|
||||
MB_FUNC_WRITE_MULTIPLE_REGISTERS :
|
||||
MB_FUNC_READ_HOLDING_REGISTER;
|
||||
break;
|
||||
case MB_PARAM_INPUT:
|
||||
command = MB_FUNC_READ_INPUT_REGISTER;
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
command = (mode == MB_PARAM_WRITE) ?
|
||||
MB_FUNC_WRITE_MULTIPLE_COILS :
|
||||
MB_FUNC_READ_COILS;
|
||||
break;
|
||||
case MB_PARAM_DISCRETE:
|
||||
if (mode != MB_PARAM_WRITE) {
|
||||
command = MB_FUNC_READ_DISCRETE_INPUTS;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: Incorrect mode (%u)",
|
||||
__FUNCTION__, (unsigned)mode);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "%s: Incorrect param type (%u)",
|
||||
__FUNCTION__, (unsigned)param_type);
|
||||
break;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
|
||||
// Helper to search parameter in the parameter description table and fills Modbus request fields accordingly
|
||||
static esp_err_t mbc_tcp_master_set_request(void *ctx, uint16_t cid, mb_param_mode_t mode, mb_param_request_t *request,
|
||||
mb_parameter_descriptor_t *reg_data)
|
||||
{
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
esp_err_t error = ESP_ERR_NOT_FOUND;
|
||||
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb incorrect request parameter.");
|
||||
MB_RETURN_ON_FALSE((mode <= MB_PARAM_WRITE), ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode.");
|
||||
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size), ESP_ERR_INVALID_ARG, TAG, "mb incorrect cid parameter.");
|
||||
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table), ESP_ERR_INVALID_ARG, TAG, "mb data dictionary is incorrect.");
|
||||
const mb_parameter_descriptor_t *reg_ptr = mbm_opts->param_descriptor_table;
|
||||
reg_ptr += cid;
|
||||
if (reg_ptr->cid == cid) {
|
||||
request->slave_addr = reg_ptr->mb_slave_addr;
|
||||
request->reg_start = reg_ptr->mb_reg_start;
|
||||
request->reg_size = reg_ptr->mb_size;
|
||||
request->command = mbc_tcp_master_get_command(reg_ptr->mb_param_type, mode);
|
||||
MB_RETURN_ON_FALSE((request->command > 0), ESP_ERR_INVALID_ARG, TAG, "mb incorrect command or parameter type.");
|
||||
if (reg_data) {
|
||||
*reg_data = *reg_ptr; // Set the cid registered parameter data
|
||||
}
|
||||
error = ESP_OK;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Get parameter data for corresponding characteristic
|
||||
static esp_err_t mbc_tcp_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
||||
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
|
||||
request.slave_addr, MB_SOCK_STATE_CONNECTED);
|
||||
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
|
||||
"mb can not send request for cid #%u with uid = %d.",
|
||||
(unsigned)reg_info.cid, (int)request.slave_addr);
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
// alloc buffer to store parameter data
|
||||
pdata = calloc(1, (reg_info.mb_size << 1));
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
error = mbc_tcp_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
||||
if (value) {
|
||||
error = mbc_master_set_param_data((void *)value, (void *)pdata,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
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));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// 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);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Get parameter data for corresponding characteristic
|
||||
static esp_err_t mbc_tcp_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
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 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid)) {
|
||||
// check that the requested uid is connected (call to port iface)
|
||||
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
|
||||
uid, MB_SOCK_STATE_CONNECTED);
|
||||
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
|
||||
"mb can not send request for cid #%u with uid=%d.", (unsigned)reg_info.cid, (int)uid);
|
||||
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
|
||||
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
||||
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
||||
}
|
||||
request.slave_addr = uid; // override the UID
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
// alloc buffer to store parameter data
|
||||
pdata = calloc(1, (reg_info.mb_size << 1));
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
error = mbc_tcp_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
||||
if (value) {
|
||||
error = mbc_master_set_param_data((void *)value, (void *)pdata,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
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));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// Set the type of parameter found in the table
|
||||
*type = reg_info.param_type;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: The cid(%u) address information is not found in the data dictionary.",
|
||||
__FUNCTION__, (unsigned)reg_info.cid);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Set parameter value for characteristic selected by name and cid
|
||||
static esp_err_t mbc_tcp_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
||||
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type 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 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
||||
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
|
||||
request.slave_addr, MB_SOCK_STATE_CONNECTED);
|
||||
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
|
||||
"mb can not send request for cid #%u with uid=%d.",
|
||||
(unsigned)reg_info.cid, (int)request.slave_addr);
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Transfer value of characteristic into parameter buffer
|
||||
error = mbc_master_set_param_data((void *)pdata, (void *)value,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
free(pdata);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Send request to write characteristic data
|
||||
error = mbc_tcp_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// Set the type of parameter found in the table
|
||||
*type = reg_info.param_type;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
||||
__FUNCTION__, (unsigned)reg_info.cid);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Set parameter value for characteristic selected by name and cid
|
||||
static esp_err_t mbc_tcp_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
||||
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type 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 };
|
||||
uint8_t *pdata = NULL;
|
||||
|
||||
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
||||
if ((error == ESP_OK) && (cid == reg_info.cid)) {
|
||||
// check that the requested uid is connected (call to port iface)
|
||||
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
|
||||
uid, MB_SOCK_STATE_CONNECTED);
|
||||
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
|
||||
"mb can not send request for cid #%d with uid=%d.",
|
||||
(unsigned)reg_info.cid, (int)uid);
|
||||
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
|
||||
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
||||
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
||||
}
|
||||
request.slave_addr = uid; // override the UID
|
||||
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
||||
|
||||
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
||||
if (!pdata) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Transfer value of characteristic into parameter buffer
|
||||
error = mbc_master_set_param_data((void *)pdata, (void *)value,
|
||||
reg_info.param_type, reg_info.param_size);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "fail to set parameter data.");
|
||||
free(pdata);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// Send request to write characteristic data
|
||||
error = mbc_tcp_master_send_request(ctx, &request, pdata);
|
||||
if (error == ESP_OK) {
|
||||
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
||||
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
||||
}
|
||||
free(pdata);
|
||||
// Set the type of parameter found in the table
|
||||
*type = reg_info.param_type;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
||||
__FUNCTION__, (unsigned)reg_info.cid);
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Modbus controller delete function
|
||||
static esp_err_t mbc_tcp_master_delete(void *ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
||||
mb_err_enum_t mb_error = MB_ENOERR;
|
||||
|
||||
// Check the stack started bit
|
||||
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
pdMS_TO_TICKS(mbm_opts->comm_opts.tcp_opts.response_tout_ms));
|
||||
if (mbm_iface->is_active || (status & MB_EVENT_STACK_STARTED)) {
|
||||
ESP_LOGD(TAG, "mb stack is active, try to disable.");
|
||||
MB_RETURN_ON_FALSE((mbc_tcp_master_stop(ctx) == ESP_OK),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
|
||||
}
|
||||
|
||||
mbm_iface->is_active = false;
|
||||
vTaskDelete(mbm_opts->task_handle);
|
||||
mbm_opts->task_handle = NULL;
|
||||
vEventGroupDelete(mbm_opts->event_group_handle);
|
||||
mbm_opts->event_group_handle = NULL;
|
||||
|
||||
mb_error = mbm_iface->mb_base->delete(mbm_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack delete failure, returned (0x%x).", (unsigned)mb_error);
|
||||
free(mbm_iface); // free the memory allocated
|
||||
ctx = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Initialization of resources for Modbus TCP master controller
|
||||
esp_err_t mbc_tcp_master_controller_create(void ** ctx)
|
||||
{
|
||||
mbm_controller_iface_t *mbm_controller_iface = (mbm_controller_iface_t *)*ctx;
|
||||
MB_RETURN_ON_FALSE((mbm_controller_iface == NULL), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack is not destroyed.");
|
||||
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
||||
mbm_controller_iface = malloc(sizeof(mbm_controller_iface_t));
|
||||
MB_GOTO_ON_FALSE((mbm_controller_iface), ESP_ERR_INVALID_STATE, error,
|
||||
TAG, "mb stack memory allocation fail.");
|
||||
|
||||
// Initialize interface properties
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
|
||||
|
||||
// Initialization of active context of the modbus controller
|
||||
BaseType_t status = 0;
|
||||
// Parameter change notification queue
|
||||
mbm_opts->event_group_handle = xEventGroupCreate();
|
||||
MB_GOTO_ON_FALSE((mbm_opts->event_group_handle), ESP_ERR_INVALID_STATE, error, TAG, "mb event group error.");
|
||||
// Create modbus controller task
|
||||
status = xTaskCreatePinnedToCore((void *)&modbus_tcp_master_task,
|
||||
"mbm_ctrl_tcp_task",
|
||||
MB_CONTROLLER_STACK_SIZE,
|
||||
mbm_controller_iface,
|
||||
MB_CONTROLLER_PRIORITY,
|
||||
&mbm_opts->task_handle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mb controller task creation error, xTaskCreate() returns (0x%x).", (unsigned)status);
|
||||
MB_MASTER_ASSERT(mbm_opts->task_handle); // The task is created but handle is incorrect
|
||||
|
||||
// Initialize public interface methods of the interface
|
||||
mbm_controller_iface->create = mbc_tcp_master_create;
|
||||
mbm_controller_iface->delete = mbc_tcp_master_delete;
|
||||
mbm_controller_iface->start = mbc_tcp_master_start;
|
||||
mbm_controller_iface->stop = mbc_tcp_master_stop;
|
||||
mbm_controller_iface->get_cid_info = mbc_tcp_master_get_cid_info;
|
||||
mbm_controller_iface->get_parameter = mbc_tcp_master_get_parameter;
|
||||
mbm_controller_iface->get_parameter_with = mbc_tcp_master_get_parameter_with;
|
||||
mbm_controller_iface->send_request = mbc_tcp_master_send_request;
|
||||
mbm_controller_iface->set_descriptor = mbc_tcp_master_set_descriptor;
|
||||
mbm_controller_iface->set_parameter = mbc_tcp_master_set_parameter;
|
||||
mbm_controller_iface->set_parameter_with = mbc_tcp_master_set_parameter_with;
|
||||
|
||||
*ctx = mbm_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbm_controller_iface) {
|
||||
if (mbm_controller_iface->opts.task_handle) {
|
||||
vTaskDelete(mbm_controller_iface->opts.task_handle);
|
||||
mbm_controller_iface->opts.task_handle = NULL;
|
||||
}
|
||||
if (mbm_controller_iface->opts.event_group_handle) {
|
||||
vEventGroupDelete(mbm_controller_iface->opts.event_group_handle);
|
||||
mbm_controller_iface->opts.event_group_handle = NULL;
|
||||
}
|
||||
}
|
||||
free(mbm_controller_iface); // free the memory allocated
|
||||
ctx = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialization of resources for Modbus serial master controller
|
||||
esp_err_t mbc_tcp_master_create(mb_communication_info_t *config, void **ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
mbm_controller_iface_t *mbm_controller_iface = (mbm_controller_iface_t *)*ctx;
|
||||
MB_RETURN_ON_FALSE((!mbm_controller_iface), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack is not destroyed.");
|
||||
// Check communication options
|
||||
mb_tcp_opts_t tcp_opts = (mb_tcp_opts_t)config->tcp_opts;
|
||||
MB_RETURN_ON_FALSE((tcp_opts.ip_addr_table), ESP_ERR_INVALID_ARG, TAG, "mb ip table address is incorrect.");
|
||||
MB_RETURN_ON_FALSE((tcp_opts.mode == MB_TCP),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb transport protocol is incorrect.");
|
||||
MB_RETURN_ON_FALSE(((tcp_opts.addr_type == MB_IPV6) || (tcp_opts.addr_type == MB_IPV4)),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb ip address type is incorrect.");
|
||||
MB_RETURN_ON_FALSE((tcp_opts.port), ESP_ERR_INVALID_ARG, TAG, "mb port is not defined.");
|
||||
|
||||
esp_err_t ret = mbc_tcp_master_controller_create((void *)&mbm_controller_iface);
|
||||
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbc create returns (0x%x).", (int)ret);
|
||||
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
|
||||
// keep the communication options to be able to restart port driver
|
||||
mbm_opts->comm_opts = *config;
|
||||
|
||||
mbm_opts->port_type = MB_PORT_TCP_MASTER;
|
||||
tcp_opts.mode = MB_TCP; // Override mode, UDP mode is not supported
|
||||
mbm_opts->comm_opts.tcp_opts = tcp_opts;
|
||||
|
||||
// Keep the response time setting
|
||||
if (!tcp_opts.response_tout_ms) {
|
||||
mbm_opts->comm_opts.tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
|
||||
}
|
||||
|
||||
mb_err_enum_t err = MB_EILLSTATE;
|
||||
void *pinst = (void *)mbm_controller_iface; // set as descr.parent object
|
||||
|
||||
// Initialize Modbus stack using mbcontroller parameters
|
||||
if (tcp_opts.mode == MB_TCP) {
|
||||
err = mbm_tcp_create(&tcp_opts, &pinst);
|
||||
}
|
||||
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbm create returns (0x%x).", (int)ret);
|
||||
|
||||
mbm_controller_iface->mb_base = (mb_base_t *)pinst;
|
||||
|
||||
const mb_rw_callbacks_t rw_cbs = {
|
||||
.reg_input_cb = mbc_reg_input_master_cb,
|
||||
.reg_holding_cb = mbc_reg_holding_master_cb,
|
||||
.reg_coils_cb = mbc_reg_coils_master_cb,
|
||||
.reg_discrete_cb = mbc_reg_discrete_master_cb
|
||||
};
|
||||
|
||||
mbm_controller_iface->mb_base->rw_cbs = rw_cbs;
|
||||
if (!mbm_opts->comm_opts.tcp_opts.start_disconnected) {
|
||||
mbm_port_tcp_set_conn_cb(mbm_controller_iface->mb_base->port_obj,
|
||||
&mbc_tcp_master_conn_done_cb,
|
||||
(void *)mbm_controller_iface);
|
||||
}
|
||||
mbm_controller_iface->is_active = false;
|
||||
*ctx = mbm_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbm_controller_iface->mb_base) {
|
||||
mbm_controller_iface->mb_base->delete(mbm_controller_iface->mb_base);
|
||||
mbm_controller_iface->mb_base = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
34
modbus/mb_controller/tcp/mbc_tcp_master.h
Normal file
34
modbus/mb_controller/tcp/mbc_tcp_master.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_tcp_master.h Modbus controller TCP master implementation header file
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief Create Modbus Master controller and stack for TCP port
|
||||
*
|
||||
* @param[out] ctx - pointer to pointer of interface structure
|
||||
* @param[in] config - pointer to configuration structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
*/
|
||||
esp_err_t mbc_tcp_master_create(mb_communication_info_t *config, void **ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
274
modbus/mb_controller/tcp/mbc_tcp_slave.c
Normal file
274
modbus/mb_controller/tcp/mbc_tcp_slave.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_tcp_slave.c
|
||||
// Implementation of the Modbus controller TCP slave
|
||||
|
||||
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
||||
#include "esp_log.h" // for log_write
|
||||
#include "sdkconfig.h" // for KConfig values
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "esp_modbus_slave.h" // for public slave interface types
|
||||
#include "mbc_slave.h" // for private slave interface types
|
||||
#include "mbc_tcp_slave.h" // for tcp slave mb controller defines
|
||||
#include "port_tcp_common.h"
|
||||
|
||||
#include "mb_common.h" // for mb types definition
|
||||
|
||||
#if MB_TCP_ENABLED
|
||||
|
||||
static const char *TAG = "mbc_tcp.slave";
|
||||
|
||||
// Modbus task function
|
||||
static void modbus_tcp_slave_task(void *param)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(param);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(param);
|
||||
|
||||
// Main Modbus stack processing cycle
|
||||
for (;;) {
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE, // do not clear bits
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
// Check if stack started then poll for data
|
||||
if (status & MB_EVENT_STACK_STARTED) {
|
||||
(void)mbs_iface->mb_base->poll(mbs_iface->mb_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start Modbus controller start function
|
||||
static esp_err_t mbc_tcp_slave_start(void *ctx)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
|
||||
status = mbs_iface->mb_base->enable(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack enable fail, returned (0x%x).", (uint16_t)status);
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupSetBits(mbs_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
||||
mbs_iface->mb_base->descr.parent = ctx;
|
||||
mbs_iface->is_active = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Start Modbus controller stop function
|
||||
static esp_err_t mbc_tcp_slave_stop(void *ctx)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_err_enum_t status = MB_EIO;
|
||||
|
||||
status = mbs_iface->mb_base->disable(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack disable fail, returned (0x%x).", (uint16_t)status);
|
||||
// Clear the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupClearBits(mbs_opts->event_group_handle,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
||||
mbs_iface->mb_base->descr.parent = NULL;
|
||||
mbs_iface->is_active = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Blocking function to get event on parameter group change for application task
|
||||
static mb_event_group_t mbc_tcp_slave_check_event(void *ctx, mb_event_group_t group)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
MB_SLAVE_ASSERT(mbs_opts->event_group_handle);
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle, (BaseType_t)group,
|
||||
pdTRUE , pdFALSE, portMAX_DELAY);
|
||||
return (mb_event_group_t)status;
|
||||
}
|
||||
|
||||
// Function to get notification about parameter change from application task
|
||||
static esp_err_t mbc_tcp_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
|
||||
{
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
esp_err_t err = ESP_ERR_TIMEOUT;
|
||||
MB_RETURN_ON_FALSE((mbs_opts->notification_queue_handle),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb queue handle is invalid.");
|
||||
MB_RETURN_ON_FALSE(reg_info, ESP_ERR_INVALID_ARG, TAG, "mb register information is invalid.");
|
||||
BaseType_t status = xQueueReceive(mbs_opts->notification_queue_handle,
|
||||
reg_info, pdMS_TO_TICKS(timeout));
|
||||
if (status == pdTRUE) {
|
||||
err = ESP_OK;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// Modbus controller delete function
|
||||
static esp_err_t mbc_tcp_slave_delete(void *ctx)
|
||||
{
|
||||
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
|
||||
mb_err_enum_t mb_error = MB_ENOERR;
|
||||
|
||||
// Check the stack started bit
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
MB_CONTROLLER_NOTIFY_TIMEOUT);
|
||||
if (mbs_iface->is_active || (status & MB_EVENT_STACK_STARTED)) {
|
||||
ESP_LOGV(TAG, "mb stack is active, try to disable.");
|
||||
MB_RETURN_ON_FALSE((mbc_tcp_slave_stop(ctx) == ESP_OK),
|
||||
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
|
||||
}
|
||||
|
||||
mbs_iface->is_active = false;
|
||||
vTaskDelete(mbs_opts->task_handle);
|
||||
vEventGroupDelete(mbs_opts->event_group_handle);
|
||||
vQueueDelete(mbs_opts->notification_queue_handle);
|
||||
mb_error = mbs_iface->mb_base->delete(mbs_iface->mb_base);
|
||||
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack close failure returned (0x%x).", (int)mb_error);
|
||||
// free the controller will be performed in common object
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mbc_tcp_slave_controller_create(void ** ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
mbs_controller_iface_t *mbs_controller_iface = *ctx;
|
||||
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
||||
MB_RETURN_ON_FALSE((mbs_controller_iface == NULL), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack is not destroyed.");
|
||||
|
||||
mbs_controller_iface = malloc(sizeof(mbs_controller_iface_t));
|
||||
MB_GOTO_ON_FALSE((mbs_controller_iface), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb stack memory allocation fail.");
|
||||
|
||||
mb_slave_options_t *mbs_opts = &mbs_controller_iface->opts;
|
||||
mbs_opts->port_type = MB_PORT_TCP_SLAVE; // set interface port type
|
||||
|
||||
// Initialization of active context of the Modbus controller
|
||||
BaseType_t status = 0;
|
||||
// Parameter change notification queue
|
||||
mbs_opts->event_group_handle = xEventGroupCreate();
|
||||
MB_GOTO_ON_FALSE((mbs_opts->event_group_handle), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb event group error.");
|
||||
// Parameter change notification queue
|
||||
mbs_opts->notification_queue_handle = xQueueCreate(
|
||||
MB_CONTROLLER_NOTIFY_QUEUE_SIZE,
|
||||
sizeof(mb_param_info_t));
|
||||
MB_GOTO_ON_FALSE((mbs_opts->notification_queue_handle), ESP_ERR_NO_MEM, error,
|
||||
TAG, "mb notify queue creation error.");
|
||||
// Create Modbus controller task
|
||||
status = xTaskCreatePinnedToCore((void *)&modbus_tcp_slave_task,
|
||||
"mbc_tcp_slave",
|
||||
MB_CONTROLLER_STACK_SIZE,
|
||||
mbs_controller_iface,
|
||||
MB_CONTROLLER_PRIORITY,
|
||||
&mbs_opts->task_handle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mb controller task creation error, xTaskCreate() returns (0x%x).", (uint16_t)status);
|
||||
// The task is created but handle is incorrect
|
||||
MB_SLAVE_ASSERT(mbs_opts->task_handle);
|
||||
|
||||
// Initialization of interface pointers
|
||||
mbs_controller_iface->create = mbc_tcp_slave_create;
|
||||
mbs_controller_iface->delete = mbc_tcp_slave_delete;
|
||||
mbs_controller_iface->start = mbc_tcp_slave_start;
|
||||
mbs_controller_iface->check_event = mbc_tcp_slave_check_event;
|
||||
mbs_controller_iface->get_param_info = mbc_tcp_slave_get_param_info;
|
||||
mbs_controller_iface->set_descriptor = NULL; // Use common descriptor setter
|
||||
*ctx = mbs_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbs_controller_iface) {
|
||||
if (mbs_controller_iface->opts.task_handle) {
|
||||
vTaskDelete(mbs_controller_iface->opts.task_handle);
|
||||
mbs_controller_iface->opts.task_handle = NULL;
|
||||
}
|
||||
if (mbs_controller_iface->opts.event_group_handle) {
|
||||
vEventGroupDelete(mbs_controller_iface->opts.event_group_handle);
|
||||
mbs_controller_iface->opts.event_group_handle = NULL;
|
||||
}
|
||||
}
|
||||
free(mbs_controller_iface); // free the memory allocated
|
||||
ctx = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialization of Modbus controller
|
||||
esp_err_t mbc_tcp_slave_create(mb_communication_info_t *config, void **ctx)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack init interface fail.");
|
||||
mbs_controller_iface_t *mbs_controller_iface = (mbs_controller_iface_t *)*ctx;
|
||||
MB_RETURN_ON_FALSE((!mbs_controller_iface), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb stack is not destroyed.");
|
||||
// Check communication options
|
||||
mb_tcp_opts_t tcp_opts = (mb_tcp_opts_t)config->tcp_opts;
|
||||
MB_RETURN_ON_FALSE(((tcp_opts.mode == MB_TCP) || (tcp_opts.mode == MB_UDP)),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb transport protocol is incorrect.");
|
||||
MB_RETURN_ON_FALSE(((tcp_opts.addr_type == MB_IPV6) || (tcp_opts.addr_type == MB_IPV4)),
|
||||
ESP_ERR_INVALID_ARG, TAG, "mb ip address type is incorrect.");
|
||||
MB_RETURN_ON_FALSE((tcp_opts.port), ESP_ERR_INVALID_ARG, TAG, "mb port is not defined.");
|
||||
|
||||
esp_err_t ret = mbc_tcp_slave_controller_create((void *)&mbs_controller_iface);
|
||||
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbc create returns (0x%x).", (uint16_t)ret);
|
||||
|
||||
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(mbs_controller_iface);
|
||||
// keep the communication options to be able to restart port driver
|
||||
mbs_opts->comm_opts = *config;
|
||||
|
||||
mbs_opts->port_type = MB_PORT_TCP_SLAVE;
|
||||
tcp_opts.mode = MB_TCP; // Override mode, UDP mode is not supported
|
||||
// Keep the response time setting
|
||||
if (!tcp_opts.response_tout_ms) {
|
||||
tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
|
||||
}
|
||||
// Set default values of communication options
|
||||
if (!tcp_opts.port) {
|
||||
mbs_opts->comm_opts.tcp_opts.port = MB_TCP_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
mbs_opts->comm_opts.tcp_opts = tcp_opts;
|
||||
mb_err_enum_t err = MB_ENOERR;
|
||||
void *pinst = (void *)mbs_controller_iface;
|
||||
|
||||
// Initialize Modbus stack using mbcontroller parameters
|
||||
err = mbs_tcp_create(&tcp_opts, &pinst);
|
||||
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
|
||||
"mbscreate returns (0x%x).", (uint16_t)err);
|
||||
mbs_controller_iface->mb_base = (mb_base_t *)pinst;
|
||||
mbs_controller_iface->mb_base->descr.is_master = false;
|
||||
|
||||
// Configure Modbus read/write callbacks for the base modbus object
|
||||
const mb_rw_callbacks_t rw_cbs = {
|
||||
.reg_input_cb = mbc_reg_input_slave_cb,
|
||||
.reg_holding_cb = mbc_reg_holding_slave_cb,
|
||||
.reg_coils_cb = mbc_reg_coils_slave_cb,
|
||||
.reg_discrete_cb = mbc_reg_discrete_slave_cb
|
||||
};
|
||||
mbs_controller_iface->mb_base->rw_cbs = rw_cbs;
|
||||
mbs_controller_iface->is_active = false;
|
||||
*ctx = (void *)mbs_controller_iface;
|
||||
return ESP_OK;
|
||||
|
||||
error:
|
||||
if (mbs_controller_iface->mb_base) {
|
||||
mbs_controller_iface->mb_base->delete(mbs_controller_iface->mb_base);
|
||||
mbs_controller_iface->mb_base = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif //#if MB_TCP_ENABLED
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user