add modbus component files

This commit is contained in:
aleks
2023-12-10 15:10:43 +01:00
parent eacef70af8
commit 1a5c957e22
85 changed files with 15129 additions and 0 deletions

55
.gitignore vendored Normal file
View 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/

300
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,300 @@
stages:
- build
- target_test
- deploy
variables:
# System environment
TARGET_TEST_ENV_IMAGE: "$CI_DOCKER_REGISTRY/target-test-env-v5.2:2"
ESP_DOCS_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env-v5.0:2-2"
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
.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 "${IDF_VERSION}"
fi
.build_cur_folder: &build_cur_folder |
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}
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
artifacts:
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
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"
build_idf_master:
extends: .build_pytest_template
image: espressif/idf:latest
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2"
build_idf_v5.0:
extends: .build_pytest_template
image: espressif/idf:release-v5.0
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3"
build_idf_v4.4:
extends: .build_pytest_template
image: espressif/idf:release-v4.4
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c3"
.target_test_template:
image: $TARGET_TEST_ENV_IMAGE
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 --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf --upgrade
.test_template:
extends:
- .before_script_pytest_jobs
tags:
- multi_dut_modbus_${TEST_PORT}
variables:
IDF_TARGET: "esp32" # the only esp32 runners are available for now
artifacts:
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"
reports:
junit: ${TEST_DIR}/${TEST_SUBDIR}/results_${IDF_VER%-*}_${TEST_SUBDIR%%/*}_${TEST_PORT}.xml
when: always
expire_in: 1 week
script:
- cd ${TEST_DIR}/${TEST_SUBDIR}/
- export IDF_VER=$(cat ${TEST_DIR}/idf_version_info.txt)
- echo "Start test for [${IDF_VER%-*}_${TEST_SUBDIR%%/*}-${TEST_SUBDIR##*/}_${TEST_PORT}]"
- python -m pytest --junit-xml=${TEST_DIR}/${TEST_SUBDIR}/results_${IDF_VER%-*}_${TEST_SUBDIR%%/*}_${TEST_PORT}.xml --target=${IDF_TARGET}
- ls -lh > test_dir_${PWD##*/}.txt
target_test_master:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.3:1"
extends: .test_template
parallel:
matrix:
- TEST_PORT: ["serial"]
TEST_SUBDIR: ["examples/serial", "test_apps"] # test only serial examples for now
needs:
job: build_idf_master
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:
- TEST_PORT: ["serial"]
TEST_SUBDIR: ["examples/serial", "test_apps"]
needs:
job: build_idf_v5.0
artifacts: true
after_script: []
target_test_v4.4:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.0:3"
extends: .test_template
parallel:
matrix:
- TEST_PORT: ["serial"]
TEST_SUBDIR: ["examples/serial", "test_apps"]
needs:
job: build_idf_v4.4
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)
- pip install -r ${CI_PROJECT_DIR}/docs/requirements.txt
- deploy-docs
deploy_docs_preview:
extends:
- .deploy_docs_template
except:
refs:
- master
variables:
TYPE: "preview"
DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/"
DOCS_DEPLOY_PRIVATEKEY: "$DOCS_DEPLOY_KEY"
DOCS_DEPLOY_SERVER: "$DOCS_SERVER"
DOCS_DEPLOY_SERVER_USER: "$DOCS_SERVER_USER"
DOCS_DEPLOY_PATH: "$DOCS_PATH"
DOCS_DEPLOY_URL_BASE: "https://$DOCS_PREVIEW_SERVER_URL/docs/esp-modbus"
# deploy_docs_production:
# extends:
# - .deploy_docs_template
# only:
# refs:
# - master
# 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 == "master"'
# - if: '$FORCE_PUSH_COMPONENT == "1"'
# script:
# - pip install idf-component-manager
# - export IDF_COMPONENT_API_TOKEN=${ESP_MODBUS_API_KEY}
# - python -m idf_component_manager upload-component --allow-existing --name=esp-modbus --namespace=espressif

19
.pre-commit-config.yaml Normal file
View 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

85
CMakeLists.txt Normal file
View File

@@ -0,0 +1,85 @@
# 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/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)
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.") # FATAL_ERROR
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
message(STATUS "The mdns included is: ${MB_MDNS_IS_INCLUDED}")

209
Kconfig Normal file
View File

@@ -0,0 +1,209 @@
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 6
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 50 400
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 300 4000
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 21
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.
endmenu

202
LICENSE Normal file
View 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.

63
README.md Normal file
View File

@@ -0,0 +1,63 @@
# 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://www.github.com/espressif/esp-modbus)
This library is to be used with Espressifs IoT Development Framework, [ESP_IDF](https://github.com/espressif/esp-idf). The packages from this repository are uploaded to Espressifs 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 v4.1 and later. ESP-IDF v4.x 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.
## Documentation
The documentation can be found on the link below:
* [ESP-Modbus documentation (English)](https://docs.espressif.com/projects/esp-modbus)
## 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-idf/tree/master/examples/protocols/modbus/serial/mb_slave)
- [Modbus serial master example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/serial/mb_master)
- [Modbus TCP master example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/tcp/mb_tcp_master)
- [Modbus TCP slave example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/tcp/mb_tcp_slave)
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
ESP-Modbus project is 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-2023 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
View 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) \
)

11
idf_component.yml Normal file
View File

@@ -0,0 +1,11 @@
version: "2.0.1"
description: ESP-MODBUS is the official Modbus library for Espressif SoCs.
url: https://github.com/espressif/esp-modbus
dependencies:
idf: ">=4.1"
files:
exclude:
- "docs/**/*"
- "docs"
- "test/**/*"
- "test"

23
linker.lf Normal file
View File

@@ -0,0 +1,23 @@
[mapping:esp_modbus]
archive: libesp-modbus.a
entries:
* (default)
port_event: mb_port_evt_set_err_type (noflash_text)
port_event: mb_port_evt_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_tmr_expired (noflash_text)
tcp_slave: mbs_tcp_transp_tmr_expired (noflash_text)
port_tcp_slave: mbm_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_tmr_mode (noflash_text)
port_timer: mb_port_get_cur_tmr_mode (noflash_text)
port_timer: mb_port_tmr_disable (noflash_text)
ascii_master: mbm_ascii_transp_tmr_expired (noflash_text)
ascii_slave: mbs_ascii_transp_tmr_1s_expired (noflash_text)
rtu_master: mbm_rtu_transp_tmr_35_expired (noflash_text)
rtu_slave: mbs_rtu_transp_tmr_35_expired (noflash_text)

View 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

View File

@@ -0,0 +1,438 @@
/*
* 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;
}

View 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

View 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

View 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%" PRIu32 ", %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;
}

View 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

View 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

View File

@@ -0,0 +1,167 @@
/*
* 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"
#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

View File

@@ -0,0 +1,411 @@
/*
* 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_log.h"
#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 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 */
} mb_descr_type_t;
/*!
* \brief Modbus descriptor table parameter size in bytes.
*/
typedef enum {
PARAM_SIZE_U8 = 0x01, /*!< Unsigned 8 */
PARAM_SIZE_U16 = 0x02, /*!< Unsigned 16 */
PARAM_SIZE_U32 = 0x04, /*!< Unsigned 32 */
PARAM_SIZE_FLOAT = 0x04, /*!< Float size */
PARAM_SIZE_ASCII = 0x08, /*!< ASCII size */
PARAM_SIZE_ASCII24 = 0x18, /*!< ASCII24 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);
/**
* @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);
/**
* @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);
/**
* @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);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,276 @@
/*
* 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 Set Modbus communication parameters for the controller
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param comm_info Communication parameters structure.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Incorrect parameter data
*/
esp_err_t mbc_slave_setup(void *ctx, void *comm_info);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
#ifdef __cplusplus
}
#endif

View 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"

View 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

View File

@@ -0,0 +1,79 @@
/*
* 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

View File

@@ -0,0 +1,683 @@
/*
* 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.");
}
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_evt_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_evt_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, uint8_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_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};
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER))
{
// Send request to read characteristic data
error = mbc_serial_master_send_request(ctx, &request, value_ptr);
if (error == ESP_OK)
{
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));
}
// 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__, 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};
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_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
// Send request to read characteristic data
error = mbc_serial_master_send_request(ctx, &request, value_ptr);
if (error == ESP_OK)
{
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));
}
// 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 name and cid
static esp_err_t mbc_serial_master_set_parameter(void *ctx, uint16_t cid,
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};
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER))
{
// 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));
}
// Set the type of parameter found in the table
*type = reg_info.param_type;
}
else
{
ESP_LOGE(TAG, "%s: The requested 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_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};
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_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
// 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));
}
// 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

View 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

View File

@@ -0,0 +1,292 @@
/*
* 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.");
}
}
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

View 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

View File

@@ -0,0 +1,799 @@
/*
* 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, "Modbus controller interface callback.");
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_evt_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_evt_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 function to set parameter buffer according to its type
static esp_err_t mbc_tcp_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((dest), ESP_ERR_INVALID_ARG, TAG, "incorrect parameter pointer.");
MB_RETURN_ON_FALSE((src), ESP_ERR_INVALID_ARG, TAG, "incorrect parameter pointer.");
// Transfer parameter data into value of characteristic
switch(param_type) {
case PARAM_TYPE_U8:
*((uint8_t *)dest) = *((uint8_t *)src);
break;
case PARAM_TYPE_U16:
*((uint16_t *)dest) = *((uint16_t *)src);
break;
case PARAM_TYPE_U32:
*((uint32_t*)dest) = *((uint32_t*)src);
break;
case PARAM_TYPE_FLOAT:
*((float*)dest) = *(float*)src;
break;
case PARAM_TYPE_ASCII:
memcpy(dest, src, param_size);
break;
default:
ESP_LOGE(TAG, "%s: Incorrect param type (%u).",
__FUNCTION__, (unsigned)param_type);
err = ESP_ERR_NOT_SUPPORTED;
break;
}
return err;
}
// Helper to search parameter by name 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, &reg_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_tcp_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, &reg_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_tcp_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, &reg_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_tcp_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, &reg_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_tcp_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_LOGW(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.");
}
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) || (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_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

View 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

View File

@@ -0,0 +1,273 @@
/*
* 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.");
}
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

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_tcp_slave.h Modbus controller TCP slave implementation header file
#pragma once
#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 ------------------------------------------*/
#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 Initialize Modbus controller and stack for TCP slave
*
* @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_slave_create(mb_communication_info_t *config, void **ctx);

View File

@@ -0,0 +1,224 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfunccoils.c, v 1.8 2007/02/18 23:47:16 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_slave.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_FUNC_READ_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_READ_SIZE (4)
#define MB_PDU_FUNC_READ_COILCNT_MAX (0x07D0)
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_SIZE (4)
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_FUNC_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
#define MB_PDU_FUNC_WRITE_MUL_SIZE_MIN (5)
#define MB_PDU_FUNC_WRITE_MUL_COILCNT_MAX (0x07B0)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_FUNC_READ_COILS_ENABLED > 0
mb_exception_t mbs_fn_read_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t coil_cnt;
uint8_t byte_num;
uint8_t *frame_cur;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF + 1]);
reg_addr++;
coil_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_COILCNT_OFF] << 8);
coil_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_COILCNT_OFF + 1]);
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((coil_cnt >= 1) &&
(coil_cnt < MB_PDU_FUNC_READ_COILCNT_MAX)) {
/* Set the current PDU data pointer to the beginning. */
frame_cur = &frame_ptr[MB_PDU_FUNC_OFF];
*len_buf = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*frame_cur++ = MB_FUNC_READ_COILS;
*len_buf += 1;
/* Test if the quantity of coils is a multiple of 8. If not last
* byte is only partially field with unused coils set to zero. */
if ((coil_cnt & 0x0007) != 0) {
byte_num = (uint8_t)(coil_cnt / 8 + 1);
} else {
byte_num = (uint8_t)(coil_cnt / 8);
}
*frame_cur++ = byte_num;
*len_buf += 1;
if (inst->rw_cbs.reg_coils_cb) {
reg_status = inst->rw_cbs.reg_coils_cb(inst, frame_cur, reg_addr, coil_cnt, MB_REG_READ);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
/* The response contains the function code, the starting address
* and the quantity of registers. We reuse the old values in the
* buffer because they are still valid. */
*len_buf += byte_num;;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid read coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#if MB_FUNC_WRITE_COIL_ENABLED > 0
mb_exception_t mbs_fn_write_coil(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint8_t buf[2];
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF + 1]);
reg_addr++;
if ((frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF + 1] == 0x00) &&
((frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0xFF) ||
(frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0x00))) {
buf[1] = 0;
if (frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0xFF) {
buf[0] = 1;
} else {
buf[0] = 0;
}
if (inst->rw_cbs.reg_coils_cb) {
reg_status = inst->rw_cbs.reg_coils_cb(inst, &buf[0], reg_addr, 1, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid write coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
mb_exception_t mbs_fn_write_multi_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t coil_cnt;
uint8_t byte_cnt;
uint8_t byte_cnt_verify;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf > (MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1]);
reg_addr++;
coil_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF] << 8);
coil_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF + 1]);
byte_cnt = frame_ptr[MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF];
/* Compute the number of expected bytes in the request. */
if ((coil_cnt & 0x0007) != 0) {
byte_cnt_verify = (uint8_t)(coil_cnt / 8 + 1);
} else {
byte_cnt_verify = (uint8_t)(coil_cnt / 8);
}
if ((coil_cnt >= 1) &&
(coil_cnt <= MB_PDU_FUNC_WRITE_MUL_COILCNT_MAX) &&
(byte_cnt_verify == byte_cnt)) {
if (inst->rw_cbs.reg_coils_cb) {
reg_status = inst->rw_cbs.reg_coils_cb(inst, &frame_ptr[MB_PDU_FUNC_WRITE_MUL_VALUES_OFF], reg_addr, coil_cnt, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
/* The response contains the function code, the starting address
* and the quantity of registers. We reuse the old values in the
* buffer because they are still valid. */
*len_buf = MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid write coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#endif

View File

@@ -0,0 +1,335 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (C) 2013 Armink <armink.ztl@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfunccoils_m.c, v 1.60 2013/10/12 15:10:12 Armink Add Master Functions
*/
#include "mb_common.h"
#include "mb_proto.h"
#include "transport_common.h"
#include "mb_master.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READ_SIZE (4)
#define MB_PDU_FUNC_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
#define MB_PDU_REQ_WRITE_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_REQ_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_SIZE (4)
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_SIZE (4)
#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_REQ_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN (5)
#define MB_PDU_REQ_WRITE_MUL_COILCNT_MAX (0x07B0)
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_MUL_SIZE (5)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED
#if MB_FUNC_READ_COILS_ENABLED
/**
* This function will request read coil.
*
* @param snd_addr salve address
* @param coil_addr coil start address
* @param coil_num coil total number
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_read_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (snd_addr > MB_ADDRESS_MAX) {
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_READ_COILS;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] = coil_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] = coil_addr;
mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF] = coil_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF + 1] = coil_num;
inst->set_send_len(inst, MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START) );
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_read_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint8_t byte_cnt;
uint8_t *mb_frame_ptr;
uint16_t reg_addr;
uint16_t coil_cnt;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
if (inst->transp_obj->frm_is_bcast(inst->transp_obj)) {
status = MB_EX_NONE;
} else if (*len_buf >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN) {
inst->get_send_buf(inst, &mb_frame_ptr);
reg_addr = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] << 8 );
reg_addr |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] );
reg_addr++;
coil_cnt = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF] << 8 );
coil_cnt |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF + 1] );
/* Test if the quantity of coils is a multiple of 8. If not last
* byte is only partially field with unused coils set to zero. */
if ((coil_cnt & 0x0007) != 0) {
byte_cnt = (uint8_t)(coil_cnt / 8 + 1);
} else {
byte_cnt = (uint8_t)(coil_cnt / 8);
}
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((coil_cnt >= 1) && (byte_cnt == frame_ptr[MB_PDU_FUNC_READ_COILCNT_OFF])) {
/* Make callback to fill the buffer. */
if (inst->rw_cbs.reg_coils_cb) {
reg_status = inst->rw_cbs.reg_coils_cb(inst, &frame_ptr[MB_PDU_FUNC_READ_VALUES_OFF], reg_addr, coil_cnt, MB_REG_READ);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid read coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
/**
* This function will request write one coil.
*
* @param snd_addr salve address
* @param coil_addr coil start address
* @param coil_data data to be written
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*
* @see mbm_rq_write_multi_coils
*/
mb_err_enum_t mbm_rq_write_coil(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_data, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (!inst || (snd_addr > MB_ADDRESS_MAX)) {
return MB_EINVAL;
}
if ((coil_data != 0xFF00) && (coil_data != 0x0000)) {
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_SINGLE_COIL;
mb_frame_ptr[MB_PDU_REQ_WRITE_ADDR_OFF] = coil_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_ADDR_OFF + 1] = coil_addr;
mb_frame_ptr[MB_PDU_REQ_WRITE_VALUE_OFF] = coil_data >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = coil_data;
inst->set_send_len(inst, (MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_SIZE));
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START) );
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_write_coil(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
(void)inst;
(void)frame_ptr;
if (*len_buf == (MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN)) {
status = MB_EX_NONE;
} else {
/* Can't be a valid write coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
/**
* This function will request write multiple coils.
*
* @param snd_addr salve address
* @param coil_addr coil start address
* @param coil_num coil total number
* @param coil_data data to be written
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*
* @see mbm_rq_write_coil
*/
mb_err_enum_t mbm_rq_write_multi_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint8_t *data_ptr, uint32_t tout)
{
uint8_t *mb_frame_ptr;
uint8_t *mb_data_ptr;
uint16_t reg_idx;
uint8_t byte_cnt;
if (snd_addr > MB_ADDRESS_MAX) {
return MB_EINVAL;
}
if (coil_num > MB_PDU_REQ_WRITE_MUL_COILCNT_MAX) {
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_MULTIPLE_COILS;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] = coil_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1] = coil_addr;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF] = coil_num >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF + 1] = coil_num;
if ((coil_num & 0x0007) != 0) {
byte_cnt = (uint8_t)(coil_num / 8 + 1);
} else {
byte_cnt = (uint8_t)(coil_num / 8);
}
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF] = byte_cnt;
mb_data_ptr = mb_frame_ptr + MB_PDU_REQ_WRITE_MUL_VALUES_OFF;
for (reg_idx = 0; reg_idx < byte_cnt; reg_idx++) {
mb_data_ptr[reg_idx] = data_ptr[reg_idx];
}
inst->set_send_len(inst, (MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + byte_cnt));
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START) );
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_write_multi_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
uint8_t *mb_frame_ptr;
uint16_t reg_address;
uint16_t coil_cnt;
uint8_t byte_cnt;
uint8_t byte_cnt_verify;
mb_err_enum_t reg_status = MB_EILLFUNC;
/* If this request is broadcast, the *len_buf is not need check. */
if ((*len_buf == MB_PDU_FUNC_WRITE_MUL_SIZE) || inst->transp_obj->frm_is_bcast(inst->transp_obj)) {
inst->get_send_buf(inst, &mb_frame_ptr);
reg_address = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8);
reg_address |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1]);
reg_address++;
coil_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF] << 8);
coil_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF + 1]);
byte_cnt = mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF];
/* Compute the number of expected bytes in the request. */
if ((coil_cnt & 0x0007) != 0) {
byte_cnt_verify = (uint8_t)(coil_cnt / 8 + 1);
} else {
byte_cnt_verify = (uint8_t)(coil_cnt / 8);
}
if ((coil_cnt >= 1) && (byte_cnt_verify == byte_cnt)) {
if (inst->rw_cbs.reg_coils_cb) {
reg_status = inst->rw_cbs.reg_coils_cb(inst, &mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_VALUES_OFF],
reg_address, coil_cnt, MB_REG_WRITE);
}
/* If an error occured, convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid write coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif // #if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
#endif // #if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0

View File

@@ -0,0 +1,29 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncdiag.c, v 1.3 2006/12/07 22:10:34 wolti Exp $
*/

View File

@@ -0,0 +1,105 @@
/*
* FreeRTOS Modbus Libary: A Modbus serial implementation for FreeRTOS
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (C) 2006 Christian Walter <wolti@sil.at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_slave.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_FUNC_READ_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_READ_DISCCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_READ_SIZE (4)
#define MB_PDU_FUNC_READ_DISCCNT_MAX (0x07D0)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_FUNC_READ_COILS_ENABLED > 0
mb_exception_t
mbs_fn_read_discrete_inp(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t discrete_cnt;
uint8_t byte_num;
uint8_t *frame_cur;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
(void)inst;
if (*len_buf == (MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF + 1]);
reg_addr++;
discrete_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_DISCCNT_OFF] << 8);
discrete_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_DISCCNT_OFF + 1]);
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((discrete_cnt >= 1) &&
(discrete_cnt < MB_PDU_FUNC_READ_DISCCNT_MAX)) {
/* Set the current PDU data pointer to the beginning. */
frame_cur = &frame_ptr[MB_PDU_FUNC_OFF];
*len_buf = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*frame_cur++ = MB_FUNC_READ_DISCRETE_INPUTS;
*len_buf += 1;
/* Test if the quantity of coils is a multiple of 8. If not last
* byte is only partially field with unused coils set to zero. */
if ((discrete_cnt & 0x0007) != 0) {
byte_num = (uint8_t) (discrete_cnt / 8 + 1);
} else {
byte_num = (uint8_t) (discrete_cnt / 8);
}
*frame_cur++ = byte_num;
*len_buf += 1;
/* Call r/w function if defined*/
if (inst->rw_cbs.reg_discrete_cb) {
reg_status = inst->rw_cbs.reg_discrete_cb(inst, frame_cur, reg_addr, discrete_cnt);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
/* The response contains the function code, the starting address
* and the quantity of registers. We reuse the old values in the
* buffer because they are still valid. */
*len_buf += byte_num;;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid read coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif

View File

@@ -0,0 +1,141 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (C) 2013 Armink <armink.ztl@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncdisc_m.c, v 1.60 2013/10/15 8:48:20 Armink Add Master Functions Exp $
*/
#include "mb_common.h"
#include "mb_proto.h"
#include "transport_common.h"
#include "mb_master.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READ_DISCCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READ_SIZE (4)
#define MB_PDU_FUNC_READ_DISCCNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
/**
* This function will request read discrete inputs.
*
* @param snd_addr salve address
* @param discrete_addr discrete start address
* @param discrete_num discrete total number
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_read_discrete_inputs(mb_base_t *inst, uint8_t snd_addr, uint16_t discrete_addr, uint16_t discrete_num, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (!inst || (snd_addr > MB_ADDRESS_MAX)) {
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_READ_DISCRETE_INPUTS;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] = discrete_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] = discrete_addr;
mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF ] = discrete_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF + 1] = discrete_num;
inst->set_send_len(inst, MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START) );
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_read_discrete_inputs(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint8_t byte_num;
uint8_t *mb_frame_ptr;
uint16_t discrete_cnt;
uint16_t reg_address;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (inst->transp_obj->frm_is_bcast(inst->transp_obj)) {
status = MB_EX_NONE;
} else if (*len_buf >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN) {
inst->get_send_buf(inst, &mb_frame_ptr);
reg_address = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] << 8);
reg_address |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1]);
reg_address++;
discrete_cnt = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF] << 8);
discrete_cnt |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF + 1]);
/* Test if the quantity of coils is a multiple of 8. If not last
* byte is only partially field with unused coils set to zero. */
if ((discrete_cnt & 0x0007) != 0) {
byte_num = (uint8_t)(discrete_cnt / 8 + 1);
} else {
byte_num = (uint8_t)(discrete_cnt / 8);
}
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((discrete_cnt >= 1) && byte_num == frame_ptr[MB_PDU_FUNC_READ_DISCCNT_OFF]) {
/* Make callback to fill the buffer. */
if (inst->rw_cbs.reg_discrete_cb) {
reg_status = inst->rw_cbs.reg_discrete_cb(inst, &frame_ptr[MB_PDU_FUNC_READ_VALUES_OFF],
reg_address, discrete_cnt);
}
/* If an error occured convert it into a Modbus exception. */
if(reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid read coil register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#endif // #if MB_SERIAL_MASTER_RTU_ENABLED || MB_SERIAL_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED

View File

@@ -0,0 +1,271 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncholding.c, v 1.12 2007/02/18 23:48:22 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_slave.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_FUNC_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_READ_SIZE (4)
#define MB_PDU_FUNC_READ_REGCNT_MAX (0x007D)
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_SIZE (4)
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_FUNC_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
#define MB_PDU_FUNC_WRITE_MUL_SIZE_MIN (5)
#define MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX (0x0078)
#define MB_PDU_FUNC_READWRITE_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF (MB_PDU_DATA_OFF + 6)
#define MB_PDU_FUNC_READWRITE_BYTECNT_OFF (MB_PDU_DATA_OFF + 8)
#define MB_PDU_FUNC_READWRITE_WRITE_VALUES_OFF (MB_PDU_DATA_OFF + 9)
#define MB_PDU_FUNC_READWRITE_SIZE_MIN (9)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
mb_exception_t mbs_fn_write_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF + 1]);
reg_addr++;
/* Make callback to update the value. */
if (inst->rw_cbs.reg_holding_cb) {
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF], reg_addr, 1, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
mb_exception_t mbs_fn_write_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t reg_cnt;
uint8_t reg_byte_cnt;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf >= (MB_PDU_FUNC_WRITE_MUL_SIZE_MIN + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1]);
reg_addr++;
reg_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF] << 8);
reg_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF + 1]);
reg_byte_cnt = frame_ptr[MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF];
if ((reg_cnt >= 1) &&
(reg_cnt <= MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX) &&
(reg_byte_cnt == (uint8_t) (2 * reg_cnt))) {
/* Make callback to update the register values. */
if (inst->rw_cbs.reg_holding_cb) {
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_WRITE_MUL_VALUES_OFF], reg_addr, reg_cnt, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
/* The response contains the function code, the starting
* address and the quantity of registers. We reuse the
* old values in the buffer because they are still valid.
*/
*len_buf = MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
mb_exception_t mbs_fn_read_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t reg_cnt;
uint8_t *frame_cur;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF + 1]);
reg_addr++;
reg_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_REGCNT_OFF] << 8);
reg_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_REGCNT_OFF + 1]);
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((reg_cnt >= 1) && (reg_cnt <= MB_PDU_FUNC_READ_REGCNT_MAX)) {
/* Set the current PDU data pointer to the beginning. */
frame_cur = &frame_ptr[MB_PDU_FUNC_OFF];
*len_buf = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*frame_cur++ = MB_FUNC_READ_HOLDING_REGISTER;
*len_buf += 1;
/* Second byte in the response contain the number of bytes. */
*frame_cur++ = (uint8_t) (reg_cnt * 2);
*len_buf += 1;
/* Make callback to fill the buffer. */
if (inst->rw_cbs.reg_holding_cb) {
reg_status = inst->rw_cbs.reg_holding_cb(inst, frame_cur, reg_addr, reg_cnt, MB_REG_READ);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
*len_buf += reg_cnt * 2;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
mb_exception_t mbs_fn_rw_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_rd_addr;
uint16_t reg_rd_cnt;
uint16_t reg_wr_addr;
uint16_t reg_wr_cnt;
uint8_t reg_wr_byte_cnt;
uint8_t *frame_cur;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
(void)inst;
if (*len_buf >= (MB_PDU_FUNC_READWRITE_SIZE_MIN + MB_PDU_SIZE_MIN)) {
reg_rd_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_READ_ADDR_OFF] << 8U);
reg_rd_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_READ_ADDR_OFF + 1]);
reg_rd_addr++;
reg_rd_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF] << 8U);
reg_rd_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF + 1]);
reg_wr_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF] << 8U);
reg_wr_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF + 1]);
reg_wr_addr++; // increase by one
reg_wr_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF] << 8U);
reg_wr_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF + 1]);
reg_wr_byte_cnt = frame_ptr[MB_PDU_FUNC_READWRITE_BYTECNT_OFF];
if ((reg_rd_cnt >= 1) && (reg_rd_cnt <= MB_PDU_FUNC_READ_REGCNT_MAX) &&
(reg_wr_cnt >= 1) && (reg_wr_cnt <= MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX) &&
((2 * reg_wr_cnt) == reg_wr_byte_cnt)) {
/* Make callback to update the register values. */
if (inst->rw_cbs.reg_holding_cb) {
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_READWRITE_WRITE_VALUES_OFF], reg_wr_addr, reg_wr_cnt, MB_REG_WRITE);
}
if (reg_status == MB_ENOERR) {
/* Set the current PDU data pointer to the beginning. */
frame_cur = &frame_ptr[MB_PDU_FUNC_OFF];
*len_buf = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*frame_cur++ = MB_FUNC_READWRITE_MULTIPLE_REGISTERS;
*len_buf += 1;
/* Second byte in the response contain the number of bytes. */
*frame_cur++ = (uint8_t) (reg_rd_cnt * 2);
*len_buf += 1;
/* Make the read callback. */
if (inst->rw_cbs.reg_holding_cb) {
reg_status = inst->rw_cbs.reg_holding_cb(inst, frame_cur, reg_rd_addr, reg_rd_cnt, MB_REG_READ);
}
if (reg_status == MB_ENOERR) {
*len_buf += 2 * reg_rd_cnt;
}
}
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
}
return status;
}
#endif

View File

@@ -0,0 +1,477 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (C) 2013 Armink <armink.ztl@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncholding_m.c, v 1.60 2013/09/02 14:13:40 Armink Add Master Functions Exp $
*/
#include "mb_common.h"
#include "mb_proto.h"
#include "transport_common.h"
#include "mb_master.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READ_SIZE (4)
#define MB_PDU_FUNC_READ_REGCNT_MAX (0x007D)
#define MB_PDU_FUNC_READ_BYTECNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
#define MB_PDU_REQ_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_SIZE (4)
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_SIZE (4)
#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_WRITE_MUL_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN (5)
#define MB_PDU_REQ_WRITE_MUL_REGCNT_MAX (0x0078)
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_MUL_SIZE (4)
#define MB_PDU_REQ_READWRITE_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READWRITE_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF (MB_PDU_DATA_OFF + 6)
#define MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF (MB_PDU_DATA_OFF + 8)
#define MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF (MB_PDU_DATA_OFF + 9)
#define MB_PDU_REQ_READWRITE_SIZE_MIN (9)
#define MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READWRITE_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READWRITE_SIZE_MIN (1)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED
#if MB_FUNC_WRITE_HOLDING_ENABLED
/**
* This function will request write holding register.
*
* @param snd_addr salve address
* @param reg_addr register start address
* @param reg_data register data to be written
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_write_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_data, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (snd_addr > MB_ADDRESS_MAX)
{
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout))
{
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_REGISTER;
mb_frame_ptr[MB_PDU_REQ_WRITE_ADDR_OFF] = reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_ADDR_OFF + 1] = reg_addr;
mb_frame_ptr[MB_PDU_REQ_WRITE_VALUE_OFF] = reg_data >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = reg_data;
inst->set_send_len(inst, MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START));
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_write_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_SIZE_MIN + MB_PDU_FUNC_WRITE_SIZE))
{
uint16_t reg_address;
reg_address = (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8);
reg_address |= (uint16_t)(frame_ptr[MB_PDU_FUNC_WRITE_ADDR_OFF + 1]);
reg_address++;
/* Make callback to update the value. */
if (inst->rw_cbs.reg_holding_cb)
{
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_WRITE_VALUE_OFF], reg_address, 1, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR)
{
status = mb_error_to_exception(reg_status);
}
}
else
{
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
/**
* This function will request write multiple holding register.
*
* @param snd_addr salve address
* @param reg_addr register start address
* @param reg_num register total number
* @param data_ptr data to be written
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_write_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint16_t *data_ptr, uint32_t tout)
{
uint8_t *mb_frame_ptr;
uint16_t reg_idx = 0;
if (!data_ptr || !inst || (snd_addr > MB_ADDRESS_MAX))
{
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout))
{
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_MULTIPLE_REGISTERS;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] = reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1] = reg_addr;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF] = reg_num >> 8;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF + 1] = reg_num;
mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF] = reg_num * 2;
mb_frame_ptr += MB_PDU_REQ_WRITE_MUL_VALUES_OFF;
while (reg_num > reg_idx)
{
*mb_frame_ptr++ = data_ptr[reg_idx] >> 8;
*mb_frame_ptr++ = data_ptr[reg_idx++];
}
inst->set_send_len(inst, (MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + 2 * reg_num));
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START));
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_write_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
uint8_t *mb_frame_ptr;
uint16_t reg_address;
uint16_t reg_count;
uint16_t byte_count;
mb_err_enum_t reg_status = MB_EILLFUNC;
/* If this request is broadcast, the *len_buf is not need check. */
if ((*len_buf == MB_PDU_SIZE_MIN + MB_PDU_FUNC_WRITE_MUL_SIZE) || inst->transp_obj->frm_is_bcast(inst->transp_obj))
{
inst->get_send_buf(inst, &mb_frame_ptr);
reg_address = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] << 8);
reg_address |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1]);
reg_address++;
reg_count = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF] << 8);
reg_count |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF + 1]);
byte_count = mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF];
if (byte_count == 2 * reg_count)
{
/* Make callback to update the register values. */
if (inst->rw_cbs.reg_holding_cb)
{
reg_status = inst->rw_cbs.reg_holding_cb(inst, &mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_VALUES_OFF],
reg_address, reg_count, MB_REG_WRITE);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR)
{
status = mb_error_to_exception(reg_status);
}
}
else
{
status = MB_EX_ILLEGAL_DATA_VALUE;
}
}
else
{
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
/**
* This function will request read holding register.
*
* @param snd_addr salve address
* @param reg_addr register start address
* @param reg_num register total number
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_read_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (!inst || (snd_addr > MB_ADDRESS_MAX))
{
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout))
{
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_READ_HOLDING_REGISTER;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] = reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] = reg_addr;
mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF] = reg_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1] = reg_num;
inst->set_send_len(inst, (MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE));
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START));
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_read_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
uint8_t *mb_frame_ptr;
uint16_t reg_address;
uint16_t reg_count;
mb_err_enum_t reg_status = MB_EILLFUNC;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
bool is_broadcast = false;
is_broadcast = inst->transp_obj->frm_is_bcast(inst->transp_obj);
if (is_broadcast == true)
{
status = MB_EX_NONE;
}
else if (*len_buf >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN)
{
inst->get_send_buf(inst, &mb_frame_ptr);
reg_address = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] << 8);
reg_address |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1]);
reg_address++;
reg_count = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF] << 8);
reg_count |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1]);
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((reg_count >= 1) && (2 * reg_count == frame_ptr[MB_PDU_FUNC_READ_BYTECNT_OFF]))
{
/* Make callback to update the register values. */
if (inst->rw_cbs.reg_holding_cb)
{
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_READ_VALUES_OFF],
reg_address, reg_count, MB_REG_READ);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR)
{
status = mb_error_to_exception(reg_status);
}
}
else
{
status = MB_EX_ILLEGAL_DATA_VALUE;
}
}
else
{
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
/**
* This function will request read and write holding register.
*
* @param snd_addr salve address
* @param rd_reg_addr read register start address
* @param rd_reg_num read register total number
* @param data_ptr data to be written
* @param wr_reg_addr write register start address
* @param wr_reg_num write register total number
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_rw_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t rd_reg_addr,
uint16_t rd_reg_num, uint16_t *data_ptr, uint16_t wr_reg_addr, uint16_t wr_reg_num, uint32_t tout)
{
uint8_t *mb_frame_ptr;
uint16_t reg_idx = 0;
if (!data_ptr || !inst || (snd_addr > MB_ADDRESS_MAX))
{
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout))
{
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_READWRITE_MULTIPLE_REGISTERS;
mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_ADDR_OFF] = rd_reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_ADDR_OFF + 1] = rd_reg_addr;
mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF] = rd_reg_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF + 1] = rd_reg_num;
mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF] = wr_reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF + 1] = wr_reg_addr;
mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF] = wr_reg_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF + 1] = wr_reg_num;
mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF] = wr_reg_num * 2;
mb_frame_ptr += MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF;
while (wr_reg_num > reg_idx)
{
*mb_frame_ptr++ = data_ptr[reg_idx] >> 8;
*mb_frame_ptr++ = data_ptr[reg_idx++];
}
inst->set_send_len(inst, (MB_PDU_SIZE_MIN + MB_PDU_REQ_READWRITE_SIZE_MIN + 2 * wr_reg_num));
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START));
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_rw_multi_holding_regs(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
uint16_t reg_rd_cnt, reg_wr_cnt;
uint16_t reg_rd_address, reg_wr_address;
uint8_t *mb_frame_ptr;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
if (inst->transp_obj->frm_is_bcast(inst->transp_obj))
{
status = MB_EX_NONE;
}
else if (*len_buf >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READWRITE_SIZE_MIN)
{
inst->get_send_buf(inst, &mb_frame_ptr);
reg_rd_address = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_ADDR_OFF] << 8);
reg_rd_address |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_ADDR_OFF + 1]);
reg_rd_address++;
reg_rd_cnt = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF] << 8);
reg_rd_cnt |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF + 1]);
reg_wr_address = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF] << 8U);
reg_wr_address |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF + 1]);
reg_wr_address++;
reg_wr_cnt = (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF] << 8U);
reg_wr_cnt |= (uint16_t)(mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF + 1]);
if ((2 * reg_rd_cnt) == frame_ptr[MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF])
{
/* Make callback to update the register values. */
if (inst->rw_cbs.reg_holding_cb)
{
reg_status = inst->rw_cbs.reg_holding_cb(inst, &mb_frame_ptr[MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF],
reg_wr_address, reg_wr_cnt, MB_REG_WRITE);
}
if (reg_status == MB_ENOERR)
{
/* Make the read callback. */
if (inst->rw_cbs.reg_holding_cb)
{
reg_status = inst->rw_cbs.reg_holding_cb(inst, &frame_ptr[MB_PDU_FUNC_READWRITE_READ_VALUES_OFF],
reg_rd_address, reg_rd_cnt, MB_REG_READ);
}
}
if (reg_status != MB_ENOERR)
{
status = mb_error_to_exception(reg_status);
}
}
else
{
status = MB_EX_ILLEGAL_DATA_VALUE;
}
}
return status;
}
#endif
#endif // #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED

View File

@@ -0,0 +1,100 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncinput.c, v 1.10 2007/09/12 10:15:56 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_slave.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_FUNC_READ_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_READ_SIZE (4)
#define MB_PDU_FUNC_READ_REGCNT_MAX (0x007D)
#define MB_PDU_FUNC_READ_RSP_BYTECNT_OFF (MB_PDU_DATA_OFF)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_FUNC_READ_INPUT_ENABLED > 0
mb_exception_t mbs_fn_read_input_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
uint16_t reg_addr;
uint16_t reg_cnt;
uint8_t *frame_cur;
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
if (*len_buf == (MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN)) {
reg_addr = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF] << 8);
reg_addr |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_ADDR_OFF + 1]);
reg_addr++;
reg_cnt = (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_REGCNT_OFF] << 8);
reg_cnt |= (uint16_t)(frame_ptr[MB_PDU_FUNC_READ_REGCNT_OFF + 1]);
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((reg_cnt >= 1)
&& (reg_cnt < MB_PDU_FUNC_READ_REGCNT_MAX)) {
/* Set the current PDU data pointer to the beginning. */
frame_cur = &frame_ptr[MB_PDU_FUNC_OFF];
*len_buf = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*frame_cur++ = MB_FUNC_READ_INPUT_REGISTER;
*len_buf += 1;
/* Second byte in the response contain the number of bytes. */
*frame_cur++ = (uint8_t)(reg_cnt * 2);
*len_buf += 1;
if (inst->rw_cbs.reg_input_cb) {
reg_status = inst->rw_cbs.reg_input_cb(inst, frame_cur, reg_addr, reg_cnt);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
} else {
*len_buf += reg_cnt * 2;
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid read input register request because the length
* is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif

View File

@@ -0,0 +1,134 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (C) 2013 Armink <armink.ztl@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncinput_m.c, v 1.60 2013/10/12 14:23:40 Armink Add Master Functions Exp $
*/
#include "mb_common.h"
#include "mb_proto.h"
#include "transport_common.h"
#include "mb_master.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READ_SIZE (4)
#define MB_PDU_FUNC_READ_BYTECNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
#define MB_PDU_FUNC_READ_RSP_BYTECNT_OFF (MB_PDU_DATA_OFF)
/* ----------------------- Static functions ---------------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/* ----------------------- Start implementation -----------------------------*/
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED
#if MB_FUNC_WRITE_HOLDING_ENABLED
/**
* This function will request read input register.
*
* @param snd_addr salve address
* @param reg_addr register start address
* @param reg_num register total number
* @param timeout timeout (-1 will waiting forever)
*
* @return error code
*/
mb_err_enum_t mbm_rq_read_inp_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (!inst || (snd_addr > MB_ADDRESS_MAX)) {
return MB_EINVAL;
}
if (!mb_port_evt_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, snd_addr);
mb_frame_ptr[MB_PDU_FUNC_OFF] = MB_FUNC_READ_INPUT_REGISTER;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] = reg_addr >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] = reg_addr;
mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF] = reg_num >> 8;
mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1] = reg_num;
inst->set_send_len(inst, MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START) );
return mb_port_evt_wait_req_finish(inst->port_obj);
}
mb_exception_t mbm_fn_read_inp_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
mb_exception_t status = MB_EX_NONE;
mb_err_enum_t reg_status = MB_EILLFUNC;
uint16_t reg_cnt;
uint16_t reg_address;
uint8_t *mb_frame_ptr;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
if (inst->transp_obj->frm_is_bcast(inst->transp_obj)) {
status = MB_EX_NONE;
} else if (*len_buf >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN) {
inst->get_send_buf(inst, &mb_frame_ptr);
reg_address = (uint16_t)( mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF] << 8 );
reg_address |= (uint16_t)( mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1] );
reg_address++;
reg_cnt = (uint16_t)( mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF] << 8 );
reg_cnt |= (uint16_t)( mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1] );
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if ((reg_cnt >= 1) && (2 * reg_cnt == frame_ptr[MB_PDU_FUNC_READ_BYTECNT_OFF])) {
/* Make callback to fill the buffer. */
if (inst->rw_cbs.reg_input_cb) {
reg_status = inst->rw_cbs.reg_input_cb(inst, &frame_ptr[MB_PDU_FUNC_READ_VALUES_OFF], reg_address, reg_cnt);
}
/* If an error occured convert it into a Modbus exception. */
if (reg_status != MB_ENOERR) {
status = mb_error_to_exception(reg_status);
}
} else {
status = MB_EX_ILLEGAL_DATA_VALUE;
}
} else {
/* Can't be a valid request because the length is incorrect. */
status = MB_EX_ILLEGAL_DATA_VALUE;
}
return status;
}
#endif
#endif // #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED

View File

@@ -0,0 +1,68 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbfuncother.c, v 1.8 2006/12/07 22:10:34 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_slave.h"
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
/* ----------------------- Start implementation -----------------------------*/
mb_err_enum_t mb_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len)
{
mb_err_enum_t status = MB_ENOERR;
/* the first byte and second byte in the buffer is reserved for
* the parameter slv_id and the running flag. The rest of
* the buffer is available for additional data. */
if (slv_idstr_len + 2 < MB_FUNC_OTHER_REP_SLAVEID_BUF) {
inst->slave_id_len = 0;
inst->slave_id[inst->slave_id_len++] = slv_id;
inst->slave_id[inst->slave_id_len++] = (uint8_t)(is_running ? 0xFF : 0x00);
if (slv_idstr_len > 0) {
memcpy(&inst->slave_id[inst->slave_id_len], slv_idstr,
(size_t)slv_idstr_len);
inst->slave_id_len += slv_idstr_len;
}
} else {
status = MB_ENORES;
}
return status;
}
mb_exception_t mb_fn_report_slv_id(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf)
{
memcpy(&frame_ptr[MB_PDU_DATA_OFF], &inst->slave_id[0], (size_t)inst->slave_id_len);
*len_buf = (uint16_t)(MB_PDU_DATA_OFF + inst->slave_id_len);
return MB_EX_NONE;
}
#endif

View File

@@ -0,0 +1,128 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2016, 2017 Nucleron R&D LLC <main@nucleron.ru>
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbutils.c, v 1.6 2007/02/18 23:49:07 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
/* ----------------------- Defines ------------------------------------------*/
#define BITS_uint8_t 8U
/* ----------------------- Start implementation -----------------------------*/
void mb_util_set_bits(uint8_t *byte_buf, uint16_t bit_offset, uint8_t but_num, uint8_t value)
{
uint16_t word_buf;
uint16_t msk;
uint16_t byte_offset;
uint16_t pre_bits_num;
uint16_t us_val = value;
assert(but_num <= 8);
assert((size_t)BITS_uint8_t == sizeof(uint8_t) * 8);
/* Calculate byte offset for first byte containing the bit values starting
* at bit_offset. */
byte_offset = (uint16_t)((bit_offset) / BITS_uint8_t);
/* How many bits precede our bits to set. */
pre_bits_num = (uint16_t)(bit_offset - byte_offset * BITS_uint8_t);
/* Move bit field into position over bits to set */
us_val <<= pre_bits_num;
/* Prepare a mask for setting the new bits. */
msk = (uint16_t)((1 << (uint16_t) but_num) - 1);
msk <<= bit_offset - byte_offset * BITS_uint8_t;
/* copy bits into temporary storage. */
word_buf = byte_buf[byte_offset];
word_buf |= byte_buf[byte_offset + 1] << BITS_uint8_t;
/* Zero out bit field bits and then or value bits into them. */
word_buf = (uint16_t)((word_buf & (~msk)) | us_val);
/* move bits back into storage */
byte_buf[byte_offset] = (uint8_t)(word_buf & 0xFF);
byte_buf[byte_offset + 1] = (uint8_t)(word_buf >> BITS_uint8_t);
}
uint8_t mb_util_get_bits(uint8_t *byte_buf, uint16_t bit_offset, uint8_t but_num)
{
uint16_t word_buf;
uint16_t msk;
uint16_t byte_offset;
uint16_t pre_bits_num;
/* Calculate byte offset for first byte containing the bit values starting
* at bit_offset. */
byte_offset = (uint16_t)((bit_offset) / BITS_uint8_t);
/* How many bits precede our bits to set. */
pre_bits_num = (uint16_t)(bit_offset - byte_offset * BITS_uint8_t);
/* Prepare a mask for setting the new bits. */
msk = (uint16_t)((1 << (uint16_t) but_num) - 1);
/* copy bits into temporary storage. */
word_buf = byte_buf[byte_offset];
word_buf |= byte_buf[byte_offset + 1] << BITS_uint8_t;
/* throw away unneeded bits. */
word_buf >>= pre_bits_num;
/* mask away bits above the requested bitfield. */
word_buf &= msk;
return (uint8_t) word_buf;
}
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code)
{
mb_exception_t status;
switch (error_code) {
case MB_ENOERR:
status = MB_EX_NONE;
break;
case MB_ENOREG:
status = MB_EX_ILLEGAL_DATA_ADDRESS;
break;
case MB_ETIMEDOUT:
status = MB_EX_SLAVE_BUSY;
break;
default:
status = MB_EX_SLAVE_DEVICE_FAILURE;
break;
}
return status;
}

View File

@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "mb_common.h"
#include "mb_proto.h"
#include "mb_func.h"
typedef mb_err_enum_t (*reg_input_cb_fp)(mb_base_t *inst, uint8_t *reg_buff, uint16_t reg_addr, uint16_t reg_num);
typedef mb_err_enum_t (*reg_holding_cb_fp)(mb_base_t *inst, uint8_t *reg_buff, uint16_t reg_addr, uint16_t reg_num, mb_reg_mode_enum_t mode);
typedef mb_err_enum_t (*reg_coils_cb_fp)(mb_base_t *inst, uint8_t *reg_buff, uint16_t reg_addr, uint16_t coil_num, mb_reg_mode_enum_t mode);
typedef mb_err_enum_t (*reg_discrete_cb_fp)(mb_base_t *inst, uint8_t *reg_buff, uint16_t reg_addr, uint16_t disc_num);
typedef struct _mb_rw_callbacks {
reg_input_cb_fp reg_input_cb;
reg_holding_cb_fp reg_holding_cb;
reg_coils_cb_fp reg_coils_cb;
reg_discrete_cb_fp reg_discrete_cb;
} mb_rw_callbacks_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "mb_config.h"
#include "mb_frame.h"
#include "mb_types.h"
#include "port_common.h"
#include "mb_callbacks.h"
#include "mb_port_types.h"
#include "esp_log.h"
#include "sdkconfig.h"
/* Common definitions */
#ifdef __cplusplus
extern "C" {
#endif
#if __has_include("esp_check.h")
#include "esp_check.h"
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) ESP_RETURN_ON_FALSE(a, err_code, tag, format __VA_OPT__(,) __VA_ARGS__)
#define MB_GOTO_ON_ERROR(x, goto_tag, log_tag, format, ...) ESP_GOTO_ON_ERROR(x, goto_tag, log_tag, format __VA_OPT__(,) __VA_ARGS__)
#define MB_GOTO_ON_FALSE(a, err_code, goto_tag, log_tag, format, ...) ESP_GOTO_ON_FALSE(a, err_code, goto_tag, log_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)
#define MB_GOTO_ON_ERROR(x, goto_tag, log_tag, format, ...) do { \
esp_err_t err_rc_ = (x); \
if (err_rc_ != ESP_OK) { \
ESP_LOGE(log_tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
ret = err_rc_; \
goto goto_tag; \
} \
} while(0)
#define MB_GOTO_ON_FALSE(a, err_code, goto_tag, log_tag, format, ...) do { \
(void)log_tag; \
if (!(a)) { \
ESP_LOGE(log_tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
ret = (err_code); \
goto goto_tag; \
} \
} while (0)
#endif
#define MB_OBJ_FMT "%p"
#define MB_OBJ_PTR(inst) (__extension__( \
{ \
assert(inst); \
(((obj_descr_t*)(inst))->parent); \
} \
))
#define MB_BASE2PORT(inst) (__extension__( \
{ \
assert(inst); \
(((mb_base_t *)inst)->port_obj); \
} \
))
#define CAT_BUF_SIZE (100)
#define MB_STR_CAT(pref, message) (__extension__( \
{ \
static char string_buf[CAT_BUF_SIZE]; \
strncpy(string_buf, pref, (CAT_BUF_SIZE - 1)); \
strncat(string_buf, message, (CAT_BUF_SIZE - 1)); \
} \
))
typedef struct mb_base_t mb_base_t;
typedef struct mb_trans_base_t mb_trans_base_t;
typedef struct mb_port_base_t mb_port_base_t;
typedef struct _obj_descr obj_descr_t;
typedef mb_err_enum_t (*mb_delete_fp)(mb_base_t *inst);
typedef mb_err_enum_t (*mb_enable_fp)(mb_base_t *inst);
typedef mb_err_enum_t (*mb_disable_fp)(mb_base_t *inst);
typedef mb_err_enum_t (*mb_poll_fp)(mb_base_t *inst);
typedef void (*mb_set_addr_fp)(mb_base_t *inst, uint8_t dest_addr);
typedef uint8_t (*mb_get_addr_fp)(mb_base_t *inst);
typedef void (*mb_set_send_len_fp)(mb_base_t *inst, uint16_t len);
typedef uint16_t (*mb_get_send_len_fp)(mb_base_t *inst);
typedef void (*mb_get_send_buf_fp)(mb_base_t *inst, uint8_t **pbuf);
typedef enum
{
STATE_ENABLED,
STATE_DISABLED,
STATE_NOT_INITIALIZED
} mb_state_enum_t;
struct mb_base_t
{
obj_descr_t descr;
_lock_t lock; // base object lock
mb_trans_base_t *transp_obj;
mb_port_base_t *port_obj;
uint8_t slave_id[MB_FUNC_OTHER_REP_SLAVEID_BUF];
uint16_t slave_id_len;
mb_delete_fp delete;
mb_enable_fp enable;
mb_disable_fp disable;
mb_poll_fp poll;
mb_set_addr_fp set_dest_addr;
mb_get_addr_fp get_dest_addr;
mb_set_send_len_fp set_send_len;
mb_get_send_len_fp get_send_len;
mb_get_send_buf_fp get_send_buf;
mb_rw_callbacks_t rw_cbs;
};
typedef struct _port_tcp_opts mb_tcp_opts_t;
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct _port_serial_opts mb_serial_opts_t;
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
#endif
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
mb_err_enum_t mbs_delete(mb_base_t *inst);
mb_err_enum_t mbs_enable(mb_base_t *inst);
mb_err_enum_t mbs_disable(mb_base_t *inst);
mb_err_enum_t mbs_poll(mb_base_t *inst);
mb_err_enum_t mbs_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
mb_err_enum_t mbm_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
#endif
mb_err_enum_t mbm_delete(mb_base_t *inst);
mb_err_enum_t mbm_enable(mb_base_t *inst);
mb_err_enum_t mbm_disable(mb_base_t *inst);
mb_err_enum_t mbm_poll(mb_base_t *inst);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,184 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h" // for KConfig options
#ifdef __cplusplus
extern "C" {
#endif
#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#endif
#include <inttypes.h>
/* ----------------------- Defines ------------------------------------------*/
/*! \defgroup modbus_cfg Modbus Configuration
*
* Most modules in the protocol stack are completly optional and can be
* excluded. This is specially important if target resources are very small
* and program memory space should be saved.<br>
*
* All of these settings are available in the file <code>mbconfig.h</code>
*/
/*! \addtogroup modbus_cfg
* @{
*/
/*! \brief If Modbus Master ASCII support is enabled. */
#define MB_MASTER_ASCII_ENABLED (CONFIG_FMB_COMM_MODE_ASCII_EN)
/*! \brief If Modbus Master RTU support is enabled. */
#define MB_MASTER_RTU_ENABLED (CONFIG_FMB_COMM_MODE_RTU_EN)
/*! \brief If Modbus Master TCP support is enabled. */
#define MB_MASTER_TCP_ENABLED (CONFIG_FMB_COMM_MODE_TCP_EN)
/*! \brief If Modbus Slave ASCII support is enabled. */
#define MB_SLAVE_ASCII_ENABLED (CONFIG_FMB_COMM_MODE_ASCII_EN)
/*! \brief If Modbus Slave RTU support is enabled. */
#define MB_SLAVE_RTU_ENABLED (CONFIG_FMB_COMM_MODE_RTU_EN)
/*! \brief If Modbus Slave TCP support is enabled. */
#define MB_TCP_ENABLED (CONFIG_FMB_COMM_MODE_TCP_EN)
#if (!CONFIG_FMB_COMM_MODE_ASCII_EN && !CONFIG_FMB_COMM_MODE_RTU_EN && !MB_MASTER_TCP_ENABLED && !MB_TCP_ENABLED)
#error "None of Modbus communication mode is enabled. Please enable one of (ASCII, RTU, TCP) mode in Kconfig."
#endif
#ifdef ESP_IDF_VERSION
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
// Features supported from 4.4
#define MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD 1
#endif
#endif
#define MB_TIMER_USE_ISR_DISPATCH_METHOD (CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD)
/*! \brief The option is required for correct UART initialization to place handler into IRAM.
*/
#if CONFIG_UART_ISR_IN_IRAM
#define MB_PORT_SERIAL_ISR_FLAG (ESP_INTR_FLAG_IRAM)
#else
#define MB_PORT_SERIAL_ISR_FLAG (ESP_INTR_FLAG_LOWMED)
#endif
/*! \brief The option represents the serial buffer size for RTU and ASCI.
*/
#define MB_BUFFER_SIZE (CONFIG_FMB_BUFFER_SIZE)
/*! \brief The option is required for support of RTU over TCP.
*/
#define MB_TCP_UID_ENABLED (CONFIG_FMB_TCP_UID_ENABLED)
/*! \brief The option defines the queue size for event queue.
*/
#define MB_EVENT_QUEUE_SIZE (CONFIG_FMB_QUEUE_LENGTH)
/*! \brief This option defines the number of data bits per ASCII character.
*
* A parity bit is added before the stop bit which keeps the actual byte size at 10 bits.
*/
#if CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB
#define MB_ASCII_BITS_PER_SYMB (CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB)
#endif
/*! \brief The character timeout value for Modbus ASCII.
*
* The character timeout value is not fixed for Modbus ASCII and is therefore
* a configuration option. It should be set to the maximum expected delay
* time of the network.
*/
#if CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS
#define MB_ASCII_TIMEOUT_MS (CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS)
#else
#define MB_ASCII_TIMEOUT_MS 1000
#endif
/*! \brief Timeout to wait in ASCII prior to enabling transmitter.
*
* If defined the function calls vMBPortSerialDelay with the argument
* MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS to allow for a delay before
* the serial transmitter is enabled. This is required because some
* targets are so fast that there is no time between receiving and
* transmitting the frame. If the master is to slow with enabling its
* receiver then he will not receive the response correctly.
*/
#ifndef MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS (0)
#endif
/*! \brief Maximum number of Modbus functions codes the protocol stack
* should support.
*
* The maximum number of supported Modbus functions must be greater than
* the sum of all enabled functions in this file and custom function
* handlers. If set to small adding more functions will fail.
*/
#define MB_FUNC_HANDLERS_MAX (16)
/*! \brief Number of bytes which should be allocated for the <em>Report Slave ID
* </em>command.
*
* This number limits the maximum size of the additional segment in the
* report slave id function. See eMBSetSlaveID( ) for more information on
* how to set this value. It is only used if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
* is set to <code>1</code>.
*/
#define MB_FUNC_OTHER_REP_SLAVEID_BUF (32)
/*! \brief If the <em>Report Slave ID</em> function should be enabled. */
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED (CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT)
/*! \brief If the <em>Read Input Registers</em> function should be enabled. */
#define MB_FUNC_READ_INPUT_ENABLED (1)
/*! \brief If the <em>Read Holding Registers</em> function should be enabled. */
#define MB_FUNC_READ_HOLDING_ENABLED (1)
/*! \brief If the <em>Write Single Register</em> function should be enabled. */
#define MB_FUNC_WRITE_HOLDING_ENABLED (1)
/*! \brief If the <em>Write Multiple registers</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED (1)
/*! \brief If the <em>Read Coils</em> function should be enabled. */
#define MB_FUNC_READ_COILS_ENABLED (1)
/*! \brief If the <em>Write Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_COIL_ENABLED (1)
/*! \brief If the <em>Write Multiple Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED (1)
/*! \brief If the <em>Read Discrete Inputs</em> function should be enabled. */
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED (1)
/*! \brief If the <em>Read/Write Multiple Registers</em> function should be enabled. */
#define MB_FUNC_READWRITE_HOLDING_ENABLED (1)
/*! @} */
#ifdef __cplusplus
}
#endif
#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED
/*! \brief If master send a broadcast frame, the master will wait time of convert to delay,
* then master can send other frame */
#define MB_MASTER_DELAY_MS_CONVERT (CONFIG_FMB_MASTER_DELAY_MS_CONVERT)
/*! \brief If master send a frame which is not broadcast,the master will wait sometime for slave.
* And if slave is not respond in this time,the master will process this timeout error.
* Then master can send other frame */
#define MB_MASTER_TIMEOUT_MS_RESPOND (CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND)
/*! \brief The total slaves in Modbus Master system.
* \note : The slave ID must be continuous from 1.*/
#define MB_MASTER_TOTAL_SLAVE_NUM (247)
#define MB_MASTER_MIN_TIMEOUT_MS_RESPOND (50)
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "mb_config.h"
#ifdef __cplusplus
extern "C" {
#endif
/*!
* Constants which defines the format of a modbus frame. The example is
* shown for a Modbus RTU/ASCII frame. Note that the Modbus PDU is not
* dependent on the underlying transport.
*
* <code>
* <------------------------ MODBUS SERIAL LINE PDU (1) ------------------->
* <----------- MODBUS PDU (1') ---------------->
* +-----------+---------------+----------------------------+-------------+
* | Address | Function Code | Data | CRC/LRC |
* +-----------+---------------+----------------------------+-------------+
* | | | |
* (2) (3/2') (3') (4)
*
* (1) ... MB_SER_PDU_SIZE_MAX = 256
* (2) ... MB_SER_PDU_ADDR_OFF = 0
* (3) ... MB_SER_PDU_PDU_OFF = 1
* (4) ... MB_SER_PDU_SIZE_CRC = 2
*
* (1') ... MB_PDU_SIZE_MAX = 253
* (2') ... MB_PDU_FUNC_OFF = 0
* (3') ... MB_PDU_DATA_OFF = 1
* </code>
*/
/* ----------------------- Defines ------------------------------------------*/
#define MB_PDU_SIZE_MAX 253 /*!< Maximum size of a PDU. */
#define MB_PDU_SIZE_MIN 1 /*!< Function Code */
#define MB_PDU_FUNC_OFF 0 /*!< Offset of function code in PDU. */
#define MB_PDU_DATA_OFF 1 /*!< Offset for response data in PDU. */
#define MB_SER_PDU_SIZE_MAX MB_BUFFER_SIZE /*!< Maximum size of a Modbus frame. */
#define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */
#define MB_TCP_TID 0
#define MB_TCP_PID 2
#define MB_TCP_LEN 4
#define MB_TCP_UID 6
#define MB_TCP_FUNC 7
#if MB_MASTER_TCP_ENABLED
#define MB_SEND_BUF_PDU_OFF MB_TCP_FUNC
#else
#define MB_SEND_BUF_PDU_OFF MB_SER_PDU_PDU_OFF
#endif
#define MB_TCP_BUFF_MAX_SIZE MB_TCP_FUNC + MB_PDU_SIZE_MAX
#define MB_TCP_PSEUDO_ADDRESS (255)
#define MB_TCP_PROTOCOL_ID (0) /* 0 = Modbus Protocol */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "mb_common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct mb_base_t mb_base_t;
#if MB_FUNC_OTHER_REP_SLAVEID_BUF
mb_exception_t mb_fn_report_slv_id(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
mb_exception_t mbm_fn_report_slv_id(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
#endif
#if MB_FUNC_READ_INPUT_ENABLED
mb_exception_t mbs_fn_read_input_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
mb_exception_t mbm_fn_read_inp_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
mb_exception_t mbs_fn_read_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
mb_exception_t mbm_fn_read_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
mb_exception_t mbs_fn_write_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
mb_exception_t mbm_fn_write_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
mb_exception_t mbs_fn_write_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
mb_exception_t mbm_fn_write_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#if MB_FUNC_READ_COILS_ENABLED
mb_exception_t mbs_fn_read_coils(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
mb_exception_t mbm_fn_read_coils(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
mb_exception_t mbs_fn_write_coil(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
mb_exception_t mbm_fn_write_coil(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
mb_exception_t mbs_fn_write_multi_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
mb_exception_t mbm_fn_write_multi_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
mb_exception_t mbs_fn_read_discrete_inp(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
mb_exception_t mbm_fn_read_discrete_inputs(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
mb_exception_t mbs_fn_rw_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
mb_exception_t mbm_fn_rw_multi_holding_regs(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "mb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct mb_base_t mb_base_t; /*!< Type of moddus object */
mb_err_enum_t mbm_rq_read_inp_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint32_t tout);
mb_err_enum_t mbm_rq_write_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_data, uint32_t tout);
mb_err_enum_t mbm_rq_write_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_wr_addr, uint16_t *data_ptr, uint32_t tout);
mb_err_enum_t mbm_rq_read_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint32_t tout);
mb_err_enum_t mbm_rq_rw_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t rd_reg_addr,
uint16_t rd_reg_num, uint16_t *data_ptr, uint16_t wr_reg_addr, uint16_t wr_reg_num, uint32_t tout);
mb_err_enum_t mbm_rq_read_discrete_inputs(mb_base_t *inst, uint8_t snd_addr, uint16_t discrete_addr, uint16_t discrete_num, uint32_t tout);
mb_err_enum_t mbm_rq_read_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint32_t tout);
mb_err_enum_t mbm_rq_write_coil(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_data, uint32_t tout);
mb_err_enum_t mbm_rq_write_multi_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint8_t *data_ptr, uint32_t tout);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "mb_config.h"
#include "mb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum _mb_comm_mode mb_mode_type_t;
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
#include "driver/uart.h"
__attribute__((__packed__))
struct _port_serial_opts {
mb_mode_type_t mode; /*!< Modbus communication mode */
uart_port_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
uint32_t response_tout_ms; /*!< Modbus slave response timeout */
uint64_t test_tout_us; /*!< Modbus test timeout (reserved) */
uint32_t baudrate; /*!< Modbus baudrate */
uart_word_length_t data_bits; /*!< Modbus number of data bits */
uart_stop_bits_t stop_bits; /*!< Modbus number of stop bits */
uart_parity_t parity; /*!< Modbus UART parity settings */
};
typedef struct _port_serial_opts mb_serial_opts_t;
#endif
typedef enum _addr_type_enum {
MB_NOIP = 0,
MB_IPV4 = 1, /*!< TCP IPV4 addressing */
MB_IPV6 = 2 /*!< TCP IPV6 addressing */
} mb_addr_type_t;
__attribute__((__packed__))
struct _port_common_opts {
mb_mode_type_t mode; /*!< Modbus communication mode */
uint16_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
uint32_t response_tout_ms; /*!< Modbus slave response timeout */
uint64_t test_tout_us; /*!< Modbus test timeout (reserved) */
};
__attribute__((__packed__))
struct _port_tcp_opts {
mb_mode_type_t mode; /*!< Modbus communication mode */
uint16_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
uint32_t response_tout_ms; /*!< Modbus slave response timeout */
uint64_t test_tout_us; /*!< Modbus test timeout (reserved) */
mb_addr_type_t addr_type; /*!< Modbus address type */
void *ip_addr_table; /*!< Modbus address or table for connection */
void *ip_netif_ptr; /*!< Modbus network interface */
bool start_disconnected; /*!< do not wait connection to all nodes before polling */
};
typedef struct _port_tcp_opts mb_tcp_opts_t;
// The common object descriptor struture (common for mb, transport, port objects)
struct _obj_descr {
char *parent_name;
char *obj_name;
void *parent;
uint32_t inst_index;
bool is_master;
};
typedef struct _obj_descr obj_descr_t;
typedef enum _mb_sock_state {
MB_SOCK_STATE_UNDEF = 0x0000,
MB_SOCK_STATE_CLOSED,
MB_SOCK_STATE_READY,
MB_SOCK_STATE_OPENED,
MB_SOCK_STATE_RESOLVED,
MB_SOCK_STATE_CONNECTING,
MB_SOCK_STATE_CONNECTED
} mb_sock_state_t;
typedef struct _uid_info {
uint16_t index; /*!< index of the address info */
int fd; /*!< slave global FD for VFS (reserved) */
char *node_name_str; /*!< node name string (host name of slave to resolve) */
char *ip_addr_str; /*!< represents the IP address of the slave */
mb_addr_type_t addr_type; /*!< type of IP address */
uint16_t uid; /*!< slave unit ID (UID) field for MBAP frame */
uint16_t port; /*!< slave port number */
mb_comm_mode_t proto; /*!< protocol type */
mb_sock_state_t state; /*!< slave state */
void *inst; /*!< pointer to linked instance */
} mb_uid_info_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------- Defines ------------------------------------------*/
#define MB_ADDRESS_BROADCAST ( 0 ) /*! Modbus broadcast address. */
#define MB_ADDRESS_MIN ( 1 ) /*! Smallest possible slave address. */
#define MB_ADDRESS_MAX ( 247 ) /*! Biggest possible slave address. */
typedef enum _mb_commands_enum
{
MB_FUNC_NONE = ( 0 ),
MB_FUNC_READ_COILS = ( 1 ),
MB_FUNC_READ_DISCRETE_INPUTS = ( 2 ),
MB_FUNC_WRITE_SINGLE_COIL = ( 5 ),
MB_FUNC_WRITE_MULTIPLE_COILS = ( 15 ),
MB_FUNC_READ_HOLDING_REGISTER = ( 3 ),
MB_FUNC_READ_INPUT_REGISTER = ( 4 ),
MB_FUNC_WRITE_REGISTER = ( 6 ),
MB_FUNC_WRITE_MULTIPLE_REGISTERS = ( 16 ),
MB_FUNC_READWRITE_MULTIPLE_REGISTERS= ( 23 ),
MB_FUNC_DIAG_READ_EXCEPTION = ( 7 ),
MB_FUNC_DIAG_DIAGNOSTIC = ( 8 ),
MB_FUNC_DIAG_GET_COM_EVENT_CNT = ( 11 ),
MB_FUNC_DIAG_GET_COM_EVENT_LOG = ( 12 ),
MB_FUNC_OTHER_REPORT_SLAVEID = ( 17 ),
MB_FUNC_ERROR = ( 128u ),
} mb_commands_t;
/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
MB_EX_NONE = 0x00,
MB_EX_ILLEGAL_FUNCTION = 0x01,
MB_EX_ILLEGAL_DATA_ADDRESS = 0x02,
MB_EX_ILLEGAL_DATA_VALUE = 0x03,
MB_EX_SLAVE_DEVICE_FAILURE = 0x04,
MB_EX_ACKNOWLEDGE = 0x05,
MB_EX_SLAVE_BUSY = 0x06,
MB_EX_MEMORY_PARITY_ERROR = 0x08,
MB_EX_GATEWAY_PATH_FAILED = 0x0A,
MB_EX_GATEWAY_TGT_FAILED = 0x0B
} mb_exception_t;
typedef mb_exception_t (*mb_fn_handler_fp)(void *, uint8_t *frame_ptr, uint16_t *len_buf);
typedef struct
{
uint8_t func_code;
mb_fn_handler_fp handler;
} mb_fn_handler_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "mb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
mb_err_enum_t mb_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,141 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "stdbool.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------- Type definitions ---------------------------------*/
/*! \ingroup modbus
* \brief Modbus serial transmission modes (RTU/ASCII/TCP/UDP).
*
* Modbus serial supports two transmission modes. Either ASCII or RTU. RTU
* is faster but has more hardware requirements and requires a network with
* a low jitter. ASCII is slower and more reliable on slower links (E.g. modems)
* The TCP or UDP mode is used for communication over ethernet.
*/
typedef enum _mb_comm_mode
{
MB_RTU, /*!< RTU transmission mode. */
MB_ASCII, /*!< ASCII transmission mode. */
MB_TCP, /*!< TCP mode. */
MB_UDP /*!< UDP mode. */
} mb_comm_mode_t;
/*! \ingroup modbus
* \brief If register should be written or read.
*
* This value is passed to the callback functions which support either
* reading or writing register values. Writing means that the application
* registers should be updated and reading means that the modbus protocol
* stack needs to know the current register values.
*
* \see mbs_reg_holding_cb(), mbs_reg_coils_cb(), mbs_reg_holding_cb() and
* mbs_reg_input_cb().
*/
typedef enum
{
MB_REG_READ = 0x0001, /*!< Read register values and pass to protocol stack. */
MB_REG_WRITE = 0x0002, /*!< Update register values. */
} mb_reg_mode_enum_t;
/*! \ingroup modbus
* \brief Event types used by all function in the protocol stack.
*/
typedef enum _mb_event_enum {
EV_TRANS_START = 0x0001, /*!< Start of transaction. */
EV_READY = 0x0002, /*!< Startup finished. */
EV_FRAME_RECEIVED = 0x0004, /*!< Frame received. */
EV_EXECUTE = 0x0008, /*!< Execute function. */
EV_FRAME_TRANSMIT = 0x0010, /*!< Transmission started . */
EV_FRAME_SENT = 0x0020, /*!< Frame sent. */
EV_ERROR_PROCESS = 0x0040, /*!< Error process state. */
EV_MASTER_ERROR_RESPOND_TIMEOUT = 0x0080, /*!< Request respond timeout. */
EV_MASTER_ERROR_RECEIVE_DATA = 0x0100, /*!< Request receive data error. */
EV_MASTER_ERROR_EXECUTE_FUNCTION = 0x0200, /*!< Request execute function error. */
EV_MASTER_PROCESS_SUCCESS = 0x0400 /*!< Master error process. */
} mb_event_enum_t;
/*! \ingroup modbus
* \brief Error event type
*/
typedef enum _mb_err_event_enum {
EV_ERROR_INIT, /*!< No error, initial state. */
EV_ERROR_RESPOND_TIMEOUT, /*!< Slave respond timeout. */
EV_ERROR_RECEIVE_DATA, /*!< Receive frame data error. */
EV_ERROR_EXECUTE_FUNCTION, /*!< Execute function error. */
EV_ERROR_OK /*!< No error, processing completed. */
} mb_err_event_t;
typedef struct _mb_event_t {
mb_event_enum_t event; /*!< event itself. */
uint64_t trans_id; /*!< unique transaction id */
uint16_t length; /*!< length of data accociated with the event */
void *pdata; /*!< data accociated with the event */
mb_err_event_t type; /*!< error type accociated with the event */
uint64_t post_ts; /*!< timestamp of event posted */
uint64_t get_ts; /*!< timestamp of event receved */
} mb_event_t;
/*! \ingroup modbus
* \brief Errorcodes used by all function in the protocol stack.
*/
typedef enum
{
MB_ENOERR, /*!< no error. */
MB_ENOREG, /*!< illegal register address. */
MB_EINVAL, /*!< illegal argument. */
MB_EPORTERR, /*!< porting layer error. */
MB_ENORES, /*!< insufficient resources. */
MB_EIO, /*!< I/O error. */
MB_EILLSTATE, /*!< protocol stack in illegal state. */
MB_ERECVDATA, /*!< receive data error. */
MB_ETIMEDOUT, /*!< timeout error occurred. */
MB_EILLFUNC, /*!< illegal MB function. */
MB_EBUSY, /*!< master is busy now. */
MB_ENOCONN /*!< peer is not connected. */
} mb_err_enum_t;
/*! \ingroup modbus
* \brief TimerMode is Master 3 kind of Timer modes.
*/
typedef enum
{
MB_TMODE_T35, /*!< Master receive frame T3.5 timeout. */
MB_TMODE_RESPOND_TIMEOUT, /*!< Master wait respond for slave. */
MB_TMODE_CONVERT_DELAY /*!< Master sent broadcast , then delay sometime.*/
} mb_tmr_mode_enum_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,123 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbutils.h, v 1.5 2006/12/07 22:10:34 wolti Exp $
*/
#pragma once
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
/*! \defgroup modbus_utils Utilities
*
* This module contains some utility functions which can be used by
* the application. It includes some special functions for working with
* bitfields backed by a character array buffer.
*
*/
/*! \addtogroup modbus_utils
* @{
*/
/*! \brief Function to set bits in a byte buffer.
*
* This function allows the efficient use of an array to implement bitfields.
* The array used for storing the bits must always be a multiple of two
* bytes. Up to eight bits can be set or cleared in one operation.
*
* \param byte_buf A buffer where the bit values are stored. Must be a
* multiple of 2 bytes. No length checking is performed and if
* bit_offset / 8 is greater than the size of the buffer memory contents
* is overwritten.
* \param bit_offset The starting address of the bits to set. The first
* bit has the offset 0.
* \param but_num Number of bits to modify. The value must always be smaller
* than 8.
* \param values Thew new values for the bits. The value for the first bit
* starting at <code>bit_offset</code> is the LSB of the value
* <code>values</code>
*
* \code
* ucBits[2] = {0, 0};
*
* // Set bit 4 to 1 (read: set 1 bit starting at bit offset 4 to value 1)
* mb_util_set_bits(ucBits, 4, 1, 1);
*
* // Set bit 7 to 1 and bit 8 to 0.
* mb_util_set_bits(ucBits, 7, 2, 0x01);
*
* // Set bits 8 - 11 to 0x05 and bits 12 - 15 to 0x0A;
* mb_util_set_bits(ucBits, 8, 8, 0x5A);
* \endcode
*/
void mb_util_set_bits(uint8_t *byte_buf, uint16_t bit_offset, uint8_t but_num, uint8_t values);
/*! \brief Function to read bits in a byte buffer.
*
* This function is used to extract up bit values from an array. Up to eight
* bit values can be extracted in one step.
*
* \param byte_buf A buffer where the bit values are stored.
* \param bit_offset The starting address of the bits to set. The first
* bit has the offset 0.
* \param but_num Number of bits to modify. The value must always be smaller
* than 8.
*
* \code
* uint8_t ucBits[2] = {0, 0};
* uint8_t ucResult;
*
* // Extract the bits 3 - 10.
* ucResult = mb_util_get_bits(ucBits, 3, 8);
* \endcode
*/
uint8_t mb_util_get_bits(uint8_t *byte_buf, uint16_t bit_offset, uint8_t but_num);
/*! \brief Standard function to set slave ID in the modbus object.
*
* This function is used to set the Slave ID array for modbus object.
* This ID can then be read over Modbus.
*
* \param inst - instance pointer to base modbus object
* \param slv_id - slave short address.
* \param is_running - true, if the slave is running, false otherwise
* \param slv_idstr - the pointer to slave ID array to set in the modbus object
* \param slv_idstr_len - slave ID array length
*
* returns the modbus error code = MB_ENOERR, if set correctly, MB_ENOREG, otherwise
* \endcode
*/
mb_err_enum_t mb_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,554 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mb_config.h"
#include "mb_common.h"
#include "mb_proto.h"
#include "mb_func.h"
#include "mb_master.h"
#include "transport_common.h"
#include "port_common.h"
#include "ascii_transport.h"
#include "rtu_transport.h"
#include "tcp_transport.h"
static const char *TAG = "mb_object.master";
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED || MB_MASTER_TCP_ENABLED)
static const mb_fn_handler_t master_handlers[MB_FUNC_HANDLERS_MAX] =
{
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
{MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mb_fn_report_slv_id},
#endif
#if MB_FUNC_READ_INPUT_ENABLED
{MB_FUNC_READ_INPUT_REGISTER, (void *)mbm_fn_read_inp_reg},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
{MB_FUNC_READ_HOLDING_REGISTER, (void *)mbm_fn_read_holding_reg},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_write_multi_holding_reg},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
{MB_FUNC_WRITE_REGISTER, (void *)mbm_fn_write_holding_reg},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_rw_multi_holding_regs},
#endif
#if MB_FUNC_READ_COILS_ENABLED
{MB_FUNC_READ_COILS, (void *)mbm_fn_read_coils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
{MB_FUNC_WRITE_SINGLE_COIL, (void *)mbm_fn_write_coil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
{MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbm_fn_write_multi_coils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
{MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbm_fn_read_discrete_inputs},
#endif
};
typedef struct
{
mb_base_t base;
mb_comm_mode_t cur_mode;
mb_state_enum_t cur_state;
const mb_fn_handler_t *func_handlers;
uint8_t *rcv_frame;
uint8_t *snd_frame;
uint16_t pdu_snd_len;
uint8_t rcv_addr;
uint16_t pdu_rcv_len;
uint8_t func_code;
mb_exception_t exception;
uint8_t master_dst_addr;
uint64_t curr_trans_id;
} mbm_object_t;
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
mb_err_enum_t mbm_delete(mb_base_t *inst);
mb_err_enum_t mbm_enable(mb_base_t *inst);
mb_err_enum_t mbm_disable(mb_base_t *inst);
mb_err_enum_t mbm_poll(mb_base_t *inst);
static void mbm_set_pdu_send_length(mb_base_t *inst, uint16_t length);
static uint16_t mbm_get_pdu_send_length(mb_base_t *inst);
static void mbm_set_dest_addr(mb_base_t *inst, uint8_t dest_addr);
static uint8_t mbm_get_dest_addr(mb_base_t *inst);
static void mbm_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf);
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED)
typedef struct _port_serial_opts mb_serial_opts_t;
mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_obj), MB_EINVAL, TAG, "invalid options for the instance.");
MB_RETURN_ON_FALSE((ser_opts->mode == MB_RTU), MB_EILLSTATE, TAG, "incorrect mode != RTU.");
mb_err_enum_t ret = MB_ENOERR;
mbm_object_t *mbm_obj = NULL;
mbm_obj = (mbm_object_t *)calloc(1, sizeof(mbm_object_t));
MB_GOTO_ON_FALSE((mbm_obj), MB_EILLSTATE, error, TAG, "no mem for mb master instance.");
CRITICAL_SECTION_INIT(mbm_obj->base.lock);
mbm_obj->cur_state = STATE_NOT_INITIALIZED;
mbm_obj->base.delete = mbm_delete;
mbm_obj->base.enable = mbm_enable;
mbm_obj->base.disable = mbm_disable;
mbm_obj->base.poll = mbm_poll;
mbm_obj->base.set_dest_addr = mbm_set_dest_addr;
mbm_obj->base.get_dest_addr = mbm_get_dest_addr;
mbm_obj->base.set_send_len = mbm_set_pdu_send_length;
mbm_obj->base.get_send_len = mbm_get_pdu_send_length;
mbm_obj->base.get_send_buf = mbm_get_pdu_send_buf;
mbm_obj->base.descr.parent = *in_out_obj;
mbm_obj->base.descr.is_master = true;
mbm_obj->base.descr.obj_name = (char *)TAG;
mbm_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbm_obj->base.descr.parent_name, "mbm_rtu@%p", mbm_obj->base.descr.parent);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbm_obj;
ret = mbm_rtu_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
mbm_obj->cur_mode = ser_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
transp_obj->get_rx_frm(transp_obj, (uint8_t **)&mbm_obj->rcv_frame);
mbm_obj->curr_trans_id = 0;
mbm_obj->base.port_obj = transp_obj->port_obj;
mbm_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbm_obj->base);
ESP_LOGD(TAG, "created object %s", mbm_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj) {
mbm_rtu_transp_delete(transp_obj);
}
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
mb_port_get_inst_counter_dec();
return ret;
}
mb_err_enum_t mbm_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_obj), MB_EINVAL, TAG, "invalid options for the instance.");
MB_RETURN_ON_FALSE((ser_opts->mode == MB_ASCII), MB_EILLSTATE, TAG, "incorrect option mode != ASCII.");
mb_err_enum_t ret = MB_ENOERR;
mbm_object_t *mbm_obj = NULL;
mbm_obj = (mbm_object_t *)calloc(1, sizeof(mbm_object_t));
MB_GOTO_ON_FALSE((mbm_obj), MB_EILLSTATE, error, TAG, "no mem for mb master instance.");
CRITICAL_SECTION_INIT(mbm_obj->base.lock);
mbm_obj->cur_state = STATE_NOT_INITIALIZED;
mbm_obj->base.delete = mbm_delete;
mbm_obj->base.enable = mbm_enable;
mbm_obj->base.disable = mbm_disable;
mbm_obj->base.poll = mbm_poll;
mbm_obj->base.set_dest_addr = mbm_set_dest_addr;
mbm_obj->base.get_dest_addr = mbm_get_dest_addr;
mbm_obj->base.set_send_len = mbm_set_pdu_send_length;
mbm_obj->base.get_send_len = mbm_get_pdu_send_length;
mbm_obj->base.get_send_buf = mbm_get_pdu_send_buf;
mbm_obj->base.descr.parent = *in_out_obj;
mbm_obj->base.descr.is_master = true;
mbm_obj->base.descr.obj_name = (char *)TAG;
mbm_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbm_obj->base.descr.parent_name, "mbm_ascii@%p", mbm_obj->base.descr.parent);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbm_obj;
ret = mbm_ascii_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
mbm_obj->cur_mode = ser_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
transp_obj->get_rx_frm(transp_obj, (uint8_t **)&mbm_obj->rcv_frame);
mbm_obj->base.port_obj = transp_obj->port_obj; // binding of the modbus object with port objet
mbm_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbm_obj->base);
ESP_LOGD(TAG, "created object %s", mbm_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj)
{
mbm_ascii_transp_delete(transp_obj);
}
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
mb_port_get_inst_counter_dec();
return ret;
}
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj)
{
MB_RETURN_ON_FALSE((tcp_opts && in_out_obj), MB_EINVAL, TAG, "invalid options for the instance.");
MB_RETURN_ON_FALSE((tcp_opts->mode == MB_TCP), MB_EILLSTATE, TAG, "incorrect option mode != TCP.");
mb_err_enum_t ret = MB_ENOERR;
mbm_object_t *mbm_obj = NULL;
mbm_obj = (mbm_object_t *)calloc(1, sizeof(mbm_object_t));
MB_RETURN_ON_FALSE(mbm_obj, MB_EILLSTATE, TAG, "no mem for mb master instance.");
CRITICAL_SECTION_INIT(mbm_obj->base.lock);
mbm_obj->cur_state = STATE_NOT_INITIALIZED;
mbm_obj->base.delete = mbm_delete;
mbm_obj->base.enable = mbm_enable;
mbm_obj->base.disable = mbm_disable;
mbm_obj->base.poll = mbm_poll;
mbm_obj->base.set_dest_addr = mbm_set_dest_addr;
mbm_obj->base.get_dest_addr = mbm_get_dest_addr;
mbm_obj->base.set_send_len = mbm_set_pdu_send_length;
mbm_obj->base.get_send_len = mbm_get_pdu_send_length;
mbm_obj->base.get_send_buf = mbm_get_pdu_send_buf;
mbm_obj->base.descr.parent = *in_out_obj;
mbm_obj->base.descr.is_master = true;
mbm_obj->base.descr.obj_name = (char *)TAG;
mbm_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbm_obj->base.descr.parent_name, "mbm_tcp#%p", mbm_obj->base.descr.parent);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbm_obj;
ret = mbm_tcp_transp_create(tcp_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
mbm_obj->cur_mode = tcp_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
transp_obj->get_rx_frm(transp_obj, (uint8_t **)&mbm_obj->rcv_frame);
mbm_obj->base.port_obj = transp_obj->port_obj; // binding of the modbus object with port object
mbm_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbm_obj->base);
ESP_LOGD(TAG, "created object %s", mbm_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj) {
mbm_tcp_transp_delete(transp_obj);
}
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
mb_port_get_inst_counter_dec();
return ret;
}
#endif
mb_err_enum_t mbm_delete(mb_base_t *inst)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
mb_err_enum_t status = MB_ENOERR;
if (mbm_obj->cur_state == STATE_DISABLED) {
if (mbm_obj->base.transp_obj->frm_delete) {
// call destructor of the transport object
mbm_obj->base.transp_obj->frm_delete(inst->transp_obj);
}
// delete the modbus instance
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(inst->lock);
status = MB_ENOERR;
free(inst);
} else {
ESP_LOGD(TAG, "disable the instance %p first.", mbm_obj);
status = MB_EILLSTATE;
}
mb_port_get_inst_counter_dec();
return status;
}
mb_err_enum_t mbm_enable(mb_base_t *inst)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
mb_err_enum_t status = MB_ENOERR;
CRITICAL_SECTION(inst->lock)
{
if (mbm_obj->cur_state == STATE_DISABLED) {
/* Activate the protocol stack. */
mbm_obj->base.transp_obj->frm_start(mbm_obj->base.transp_obj);
mbm_obj->cur_state = STATE_ENABLED;
status = MB_ENOERR;
} else {
status = MB_EILLSTATE;
}
}
return status;
}
mb_err_enum_t mbm_disable(mb_base_t *inst)
{
mb_err_enum_t status = MB_ENOERR;
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
CRITICAL_SECTION(inst->lock)
{
if (mbm_obj->cur_state == STATE_ENABLED) {
mbm_obj->base.transp_obj->frm_stop(mbm_obj->base.transp_obj);
mbm_obj->cur_state = STATE_DISABLED;
status = MB_ENOERR;
} else if (mbm_obj->cur_state == STATE_DISABLED) {
status = MB_ENOERR;
} else {
status = MB_EILLSTATE;
}
}
return status;
}
static void mbm_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
mbm_obj->base.transp_obj->get_tx_frm(mbm_obj->base.transp_obj, pbuf);
}
__attribute__((unused))
static void mbm_get_pdu_recv_buf(mb_base_t *inst, uint8_t **pbuf)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
mbm_obj->base.transp_obj->get_rx_frm(mbm_obj->base.transp_obj, pbuf);
}
static void mbm_set_pdu_send_length(mb_base_t *inst, uint16_t length)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
CRITICAL_SECTION(inst->lock)
{
mbm_obj->pdu_snd_len = length;
}
}
static uint16_t mbm_get_pdu_send_length(mb_base_t *inst)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
return mbm_obj->pdu_snd_len;
}
static void mbm_set_dest_addr(mb_base_t *inst, uint8_t dest_addr)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
CRITICAL_SECTION(inst->lock)
{
mbm_obj->master_dst_addr = dest_addr;
}
}
static uint8_t mbm_get_dest_addr(mb_base_t *inst)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
return mbm_obj->master_dst_addr;
}
void mbm_error_cb_respond_timeout(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length)
{
mb_port_evt_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_RESPOND_TIMEOUT);
ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_WARN);
}
void mbm_error_cb_receive_data(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length)
{
mb_port_evt_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_RECEIVE_DATA);
ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG);
}
void mbm_error_cb_execute_function(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length)
{
mb_port_evt_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_EXECUTE_FUNCTION);
ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG);
}
void mbm_error_cb_request_success(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length)
{
mb_port_evt_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_PROCESS_SUCCESS);
ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG);
}
mb_err_enum_t mbm_poll(mb_base_t *inst)
{
mbm_object_t *mbm_obj = __containerof(inst, mbm_object_t, base);
uint16_t length;
mb_exception_t exception;
mb_err_enum_t status = MB_ENOERR;
mb_event_t event;
mb_err_event_t error_type;
/* Check if the protocol stack is ready. */
if (mbm_obj->cur_state != STATE_ENABLED) {
return MB_EILLSTATE;
}
/* Check if there is a event available. If not return control to caller.
* Otherwise we will handle the event. */
if (mb_port_evt_get(mbm_obj->base.port_obj, &event)) {
switch (event.event) {
case EV_READY:
ESP_LOGW(TAG, MB_OBJ_FMT":EV_READY", MB_OBJ_PTR(inst));
mb_port_evt_res_release(inst->port_obj);
break;
case EV_FRAME_TRANSMIT:
mbm_get_pdu_send_buf(inst, &mbm_obj->snd_frame);
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":MB_TRANSMIT"), (void *)mbm_obj->snd_frame, mbm_obj->pdu_snd_len, ESP_LOG_WARN);
status = inst->transp_obj->frm_send(inst->transp_obj, mbm_obj->master_dst_addr, mbm_obj->snd_frame, mbm_obj->pdu_snd_len);
if (status != MB_ENOERR) {
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_RECEIVE_DATA);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
ESP_LOGE(TAG, MB_OBJ_FMT", frame send error. %d", MB_OBJ_PTR(inst), (int)status);
}
// Initialize modbus transaction
mbm_obj->curr_trans_id = event.trans_id;
break;
case EV_FRAME_SENT:
ESP_LOGD(TAG, MB_OBJ_FMT":EV_MASTER_FRAME_SENT", MB_OBJ_PTR(inst));
break;
case EV_FRAME_RECEIVED:
mbm_obj->pdu_rcv_len = event.length;
status = inst->transp_obj->frm_rcv(inst->transp_obj, &mbm_obj->rcv_addr, &mbm_obj->rcv_frame, &mbm_obj->pdu_rcv_len);
MB_RETURN_ON_FALSE(mbm_obj->snd_frame, MB_EILLSTATE, TAG, "Send buffer initialization fail.");
if (event.trans_id == mbm_obj->curr_trans_id) {
// Check if the frame is for us. If not ,send an error process event.
if ((status == MB_ENOERR) && ((mbm_obj->rcv_addr == mbm_obj->master_dst_addr)
|| (mbm_obj->rcv_addr == MB_TCP_PSEUDO_ADDRESS))) {
if ((mbm_obj->rcv_frame[MB_PDU_FUNC_OFF] & ~MB_FUNC_ERROR) == (mbm_obj->snd_frame[MB_PDU_FUNC_OFF])) {
ESP_LOGD(TAG, MB_OBJ_FMT", frame data received successfully, (%d).", MB_OBJ_PTR(inst), (int)status);
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":MB_RECV"), (void *)mbm_obj->rcv_frame,
(uint16_t)mbm_obj->pdu_rcv_len, ESP_LOG_WARN);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_EXECUTE));
} else {
ESP_LOGE(TAG, MB_OBJ_FMT", drop incorrect frame, receive_func(%u) != send_func(%u)",
MB_OBJ_PTR(inst), (mbm_obj->rcv_frame[MB_PDU_FUNC_OFF] & ~MB_FUNC_ERROR),
mbm_obj->snd_frame[MB_PDU_FUNC_OFF]);
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_RECEIVE_DATA);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
}
} else {
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_RECEIVE_DATA);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
ESP_LOGD(TAG, MB_OBJ_FMT", packet data receive failed (addr=%u)(%u).",
MB_OBJ_PTR(inst), (unsigned)mbm_obj->rcv_addr, (unsigned)status);
}
} else {
// Ignore the `EV_FRAME_RECEIVED` event because the respond timeout occurred
// and this is likely respond to previous transaction
ESP_LOGE(TAG, MB_OBJ_FMT", drop data received outside of transaction.", MB_OBJ_PTR(inst));
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_RESPOND_TIMEOUT);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
}
break;
case EV_EXECUTE:
if (event.trans_id == mbm_obj->curr_trans_id) {
MB_RETURN_ON_FALSE(mbm_obj->rcv_frame, MB_EILLSTATE, TAG, MB_OBJ_FMT", receive buffer initialization fail.", MB_OBJ_PTR(inst));
ESP_LOGD(TAG, MB_OBJ_FMT":EV_EXECUTE", MB_OBJ_PTR(inst));
mbm_obj->func_code = mbm_obj->rcv_frame[MB_PDU_FUNC_OFF];
exception = MB_EX_ILLEGAL_FUNCTION;
/* If receive frame has exception. The receive function code highest bit is 1.*/
if (mbm_obj->func_code & MB_FUNC_ERROR) {
exception = (mb_exception_t)mbm_obj->rcv_frame[MB_PDU_DATA_OFF];
} else {
for (int i = 0; i < MB_FUNC_HANDLERS_MAX; i++) {
/* No more function handlers registered. Abort. */
if (mbm_obj->func_handlers[i].func_code == 0) {
break;
}
if (mbm_obj->func_handlers[i].func_code == mbm_obj->func_code) {
/* If master request is broadcast,
* the master need execute function for all slave.
*/
if (inst->transp_obj->frm_is_bcast(inst->transp_obj)) {
length = mbm_obj->pdu_snd_len;
for (int j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++) {
mbm_set_dest_addr(inst, j);
exception = mbm_obj->func_handlers[i].handler(inst, mbm_obj->rcv_frame, &length);
}
} else {
exception = mbm_obj->func_handlers[i].handler(inst, mbm_obj->rcv_frame, &mbm_obj->pdu_rcv_len);
}
break;
}
}
}
/* If master has exception, will send error process event. Otherwise the master is idle.*/
if (exception != MB_EX_NONE) {
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_EXECUTE_FUNCTION);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
} else {
error_type = mb_port_evt_get_err_type(inst->port_obj);
if (error_type == EV_ERROR_INIT) {
ESP_LOGD(TAG, MB_OBJ_FMT", set event EV_ERROR_OK", MB_OBJ_PTR(inst));
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_OK);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
}
}
} else {
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_EXECUTE_FUNCTION);
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_ERROR_PROCESS));
ESP_LOGE(TAG, MB_OBJ_FMT", execution is expired.", MB_OBJ_PTR(inst));
}
break;
case EV_ERROR_PROCESS:
ESP_LOGW(TAG, MB_OBJ_FMT":EV_ERROR_PROCESS", MB_OBJ_PTR(inst));
// stop timer and execute specified error process callback function.
mb_port_tmr_disable(inst->port_obj);
error_type = mb_port_evt_get_err_type(inst->port_obj);
mbm_get_pdu_send_buf(inst, &mbm_obj->snd_frame);
switch (error_type)
{
case EV_ERROR_RESPOND_TIMEOUT:
mbm_error_cb_respond_timeout(inst, mbm_obj->master_dst_addr,
mbm_obj->snd_frame, mbm_obj->pdu_snd_len);
break;
case EV_ERROR_RECEIVE_DATA:
mbm_error_cb_receive_data(inst, mbm_obj->master_dst_addr,
mbm_obj->snd_frame, mbm_obj->pdu_snd_len);
break;
case EV_ERROR_EXECUTE_FUNCTION:
mbm_error_cb_execute_function(inst, mbm_obj->master_dst_addr,
mbm_obj->snd_frame, mbm_obj->pdu_snd_len);
break;
case EV_ERROR_OK:
mbm_error_cb_request_success(inst, mbm_obj->master_dst_addr,
mbm_obj->snd_frame, mbm_obj->pdu_snd_len);
break;
default:
ESP_LOGE(TAG, MB_OBJ_FMT", incorrect error type = %d.", MB_OBJ_PTR(inst), (int)error_type);
break;
}
mb_port_evt_set_err_type(inst->port_obj, EV_ERROR_INIT);
uint64_t time_div_us = mbm_obj->curr_trans_id ? (event.get_ts - mbm_obj->curr_trans_id) : 0;
mbm_obj->curr_trans_id = 0;
ESP_LOGW(TAG, MB_OBJ_FMT", transaction processing time(us) = %" PRId64, MB_OBJ_PTR(inst), time_div_us);
mb_port_evt_res_release(inst->port_obj);
break;
default:
ESP_LOGE(TAG, MB_OBJ_FMT", unexpected event triggered 0x%02x.", MB_OBJ_PTR(inst), (int)event.event);
break;
}
} else {
// Something went wrong and task unblocked but there are no any correct events set
ESP_LOGE(TAG, MB_OBJ_FMT", unexpected event triggered 0x%02x.", MB_OBJ_PTR(inst), (int)event.event);
status = MB_EILLSTATE;
}
return status;
}
#endif

View File

@@ -0,0 +1,384 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mb_common.h"
#include "mb_proto.h"
#include "mb_func.h"
#include "mb_slave.h"
#include "transport_common.h"
#include "port_common.h"
#include "ascii_transport.h"
#include "rtu_transport.h"
#include "tcp_transport.h"
static const char *TAG = "mb_object.slave";
static mb_fn_handler_t slave_handlers[MB_FUNC_HANDLERS_MAX] =
{
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
{MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mb_fn_report_slv_id},
#endif
#if MB_FUNC_READ_INPUT_ENABLED
{MB_FUNC_READ_INPUT_REGISTER, (void *)mbs_fn_read_input_reg},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
{MB_FUNC_READ_HOLDING_REGISTER, (void *)mbs_fn_read_holding_reg},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_write_multi_holding_reg},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
{MB_FUNC_WRITE_REGISTER, (void *)mbs_fn_write_holding_reg},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_rw_multi_holding_reg},
#endif
#if MB_FUNC_READ_COILS_ENABLED
{MB_FUNC_READ_COILS, (void *)mbs_fn_read_coils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
{MB_FUNC_WRITE_SINGLE_COIL, (void *)mbs_fn_write_coil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
{MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbs_fn_write_multi_coils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
{MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbs_fn_read_discrete_inp},
#endif
};
typedef struct
{
mb_base_t base;
// here are slave object properties and methods
uint8_t mb_address;
mb_comm_mode_t cur_mode;
mb_state_enum_t cur_state;
mb_fn_handler_t *func_handlers;
uint8_t *frame;
uint16_t length;
uint8_t func_code;
uint8_t rcv_addr;
uint64_t curr_trans_id;
volatile uint16_t *pdu_snd_len;
} mbs_object_t;
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
mb_err_enum_t mbs_delete(mb_base_t *inst);
mb_err_enum_t mbs_enable(mb_base_t *inst);
mb_err_enum_t mbs_disable(mb_base_t *inst);
mb_err_enum_t mbs_poll(mb_base_t *inst);
mb_err_enum_t mbs_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct _port_serial_opts mb_serial_opts_t;
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
{
mb_err_enum_t ret = MB_ENOERR;
MB_RETURN_ON_FALSE(ser_opts, MB_EINVAL, TAG, "invalid options for the instance.");
MB_RETURN_ON_FALSE((ser_opts->mode == MB_RTU), MB_EILLSTATE, TAG, "incorrect mode != RTU.");
mbs_object_t *mbs_obj = NULL;
mbs_obj = (mbs_object_t*)calloc(1, sizeof(mbs_object_t));
MB_GOTO_ON_FALSE((mbs_obj), MB_EILLSTATE, error, TAG, "no mem for mb slave instance.");
CRITICAL_SECTION_INIT(mbs_obj->base.lock);
mbs_obj->cur_state = STATE_NOT_INITIALIZED;
mbs_obj->base.delete = mbs_delete;
mbs_obj->base.enable = mbs_enable;
mbs_obj->base.disable = mbs_disable;
mbs_obj->base.poll = mbs_poll;
mbs_obj->base.descr.parent = *in_out_obj;
mbs_obj->base.descr.is_master = false;
mbs_obj->base.descr.obj_name = (char *)TAG;
mbs_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbs_obj->base.descr.parent_name, "mbs_rtu@%p", *in_out_obj);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbs_obj;
ret = mbs_rtu_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
mbs_obj->cur_mode = ser_opts->mode;
mbs_obj->mb_address = ser_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbs_obj->frame);
mbs_obj->base.port_obj = transp_obj->port_obj;
mbs_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbs_obj->base);
ESP_LOGD(TAG, "created object %s", mbs_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj) {
mbs_rtu_transp_delete(transp_obj);
}
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
mb_port_get_inst_counter_dec();
return ret;
}
mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
{
mb_err_enum_t ret = MB_ENOERR;
MB_RETURN_ON_FALSE(ser_opts, MB_EINVAL, TAG, "invalid options for %s instance.", TAG);
MB_RETURN_ON_FALSE((ser_opts->mode == MB_ASCII), MB_EILLSTATE, TAG, "incorrect mode != ASCII.");
mbs_object_t *mbs_obj = NULL;
mbs_obj = (mbs_object_t*)calloc(1, sizeof(mbs_object_t));
MB_GOTO_ON_FALSE((mbs_obj), MB_EILLSTATE, error, TAG, "no mem for mb slave instance.");
CRITICAL_SECTION_INIT(mbs_obj->base.lock);
mbs_obj->base.delete = mbs_delete;
mbs_obj->base.enable = mbs_enable;
mbs_obj->base.disable = mbs_disable;
mbs_obj->base.poll = mbs_poll;
mbs_obj->base.descr.parent = *in_out_obj;
mbs_obj->base.descr.is_master = false;
mbs_obj->base.descr.obj_name = (char *)TAG;
mbs_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbs_obj->base.descr.parent_name, "mbs_ascii@%p", *in_out_obj);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbs_obj;
ret = mbs_ascii_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
mbs_obj->cur_mode = ser_opts->mode;
mbs_obj->mb_address = ser_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbs_obj->frame);
mbs_obj->base.port_obj = transp_obj->port_obj;
mbs_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbs_obj->base);
ESP_LOGD(TAG, "created object %s", mbs_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj) {
mbs_ascii_transp_delete(transp_obj);
}
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
mb_port_get_inst_counter_dec();
return ret;
}
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj)
{
mb_err_enum_t ret = MB_ENOERR;
MB_RETURN_ON_FALSE(tcp_opts, MB_EINVAL, TAG, "invalid options for the instance.");
MB_RETURN_ON_FALSE((tcp_opts->mode == MB_TCP), MB_EILLSTATE, TAG, "incorrect mode != TCP.");
mbs_object_t *mbs_obj = NULL;
mbs_obj = (mbs_object_t*)calloc(1, sizeof(mbs_object_t));
MB_GOTO_ON_FALSE((mbs_obj), MB_EILLSTATE, error, TAG, "no mem for mb slave instance.");
CRITICAL_SECTION_INIT(mbs_obj->base.lock);
mbs_obj->cur_state = STATE_NOT_INITIALIZED;
mbs_obj->base.delete = mbs_delete;
mbs_obj->base.enable = mbs_enable;
mbs_obj->base.disable = mbs_disable;
mbs_obj->base.poll = mbs_poll;
mbs_obj->base.descr.parent = *in_out_obj;
mbs_obj->base.descr.is_master = false;
mbs_obj->base.descr.obj_name = (char *)TAG;
mbs_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
int res = asprintf(&mbs_obj->base.descr.parent_name, "mbs_tcp@%p", *in_out_obj);
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
TAG, "name alloc fail, err: %d", (int)res);
mb_trans_base_t *transp_obj = (mb_trans_base_t *)mbs_obj;
ret = mbs_tcp_transp_create(tcp_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
mbs_obj->cur_mode = tcp_opts->mode;
mbs_obj->mb_address = tcp_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbs_obj->frame);
mbs_obj->base.port_obj = transp_obj->port_obj;
mbs_obj->base.transp_obj = transp_obj;
*in_out_obj = (void *)&(mbs_obj->base);
ESP_LOGD(TAG, "created object %s", mbs_obj->base.descr.parent_name);
return MB_ENOERR;
error:
if (transp_obj) {
mbs_tcp_transp_delete(transp_obj);
}
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
mb_port_get_inst_counter_dec();
return ret;
}
#endif
mb_err_enum_t mbs_delete(mb_base_t *inst)
{
mbs_object_t *mbs_obj = __containerof(inst, mbs_object_t, base);
mb_err_enum_t status = MB_ENOERR;
if (mbs_obj->cur_state == STATE_DISABLED) {
if (mbs_obj->base.transp_obj->frm_delete) {
// call destructor of the transport object
mbs_obj->base.transp_obj->frm_delete(inst->transp_obj);
}
// delete the modbus instance
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(inst->lock);
free(inst);
status = MB_ENOERR;
} else {
ESP_LOGD(TAG, " need to disable %p object first.", (void *)mbs_obj);
status = MB_EILLSTATE;
}
mb_port_get_inst_counter_dec();
return status;
}
mb_err_enum_t mbs_enable(mb_base_t *inst)
{
mbs_object_t *mbs_obj = __containerof(inst, mbs_object_t, base);
mb_err_enum_t status = MB_ENOERR;
CRITICAL_SECTION(inst->lock) {
if (mbs_obj->cur_state == STATE_DISABLED) {
/* Activate the protocol stack. */
mbs_obj->base.transp_obj->frm_start(mbs_obj->base.transp_obj);
mbs_obj->cur_state = STATE_ENABLED;
status = MB_ENOERR;
} else {
status = MB_EILLSTATE;
}
}
if (!mbs_obj->mb_address) {
ESP_LOGD(TAG, "incorrect slave address in %p object.", (void *)mbs_obj);
status = MB_EINVAL;
}
return status;
}
mb_err_enum_t mbs_disable(mb_base_t *inst)
{
mb_err_enum_t status = MB_ENOERR;
mbs_object_t *mbs_obj = __containerof(inst, mbs_object_t, base);
CRITICAL_SECTION(inst->lock) {
if (mbs_obj->cur_state == STATE_ENABLED) {
mbs_obj->base.transp_obj->frm_stop(mbs_obj->base.transp_obj);
mbs_obj->cur_state = STATE_DISABLED;
status = MB_ENOERR;
} else if (mbs_obj->cur_state == STATE_DISABLED) {
status = MB_ENOERR;
} else {
status = MB_EILLSTATE;
}
}
return status;
}
mb_err_enum_t mbs_poll(mb_base_t *inst)
{
mbs_object_t *mbs_obj = __containerof(inst, mbs_object_t, base);
mb_exception_t exception;
mb_err_enum_t status = MB_ENOERR;
mb_event_t event;
/* Check if the protocol stack is ready. */
if (mbs_obj->cur_state != STATE_ENABLED) {
return MB_EILLSTATE;
}
/* Check if there is a event available. If not, return control to caller. Otherwise we will handle the event. */
if (mb_port_evt_get(mbs_obj->base.port_obj, &event)) {
switch(event.event) {
case EV_READY:
ESP_LOGW(TAG, MB_OBJ_FMT":EV_READY", MB_OBJ_PTR(inst));
mb_port_evt_res_release(inst->port_obj);
break;
case EV_FRAME_RECEIVED:
ESP_LOGD(TAG, MB_OBJ_FMT":EV_FRAME_RECEIVED", MB_OBJ_PTR(inst));
mbs_obj->length = event.length;
status = inst->transp_obj->frm_rcv(inst->transp_obj, &mbs_obj->rcv_addr, &mbs_obj->frame, &mbs_obj->length);
// Check if the frame is for us. If not ,send an error process event.
if (status == MB_ENOERR) {
// Check if the frame is for us. If not ignore the frame.
if((mbs_obj->rcv_addr == mbs_obj->mb_address) || (mbs_obj->rcv_addr == MB_ADDRESS_BROADCAST)
|| (mbs_obj->rcv_addr == MB_TCP_PSEUDO_ADDRESS)) {
mbs_obj->curr_trans_id = event.get_ts;
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_EXECUTE | EV_TRANS_START));
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":MB_RECV"), &mbs_obj->frame[MB_PDU_FUNC_OFF],
(uint16_t)mbs_obj->length, ESP_LOG_WARN);
}
}
break;
case EV_EXECUTE:
MB_RETURN_ON_FALSE(mbs_obj->frame, MB_EILLSTATE, TAG, "receive buffer fail.");
ESP_LOGD(TAG, MB_OBJ_FMT":EV_EXECUTE", MB_OBJ_PTR(inst));
mbs_obj->func_code = mbs_obj->frame[MB_PDU_FUNC_OFF];
exception = MB_EX_ILLEGAL_FUNCTION;
// If receive frame has exception. The receive function code highest bit is 1.
for (int i = 0; (i < MB_FUNC_HANDLERS_MAX); i++) {
// No more function handlers registered. Abort.
if (mbs_obj->func_handlers[i].func_code == 0) {
break;
}
if ((mbs_obj->func_handlers[i].func_code) == mbs_obj->func_code) {
if (mbs_obj->func_code == 0x04) {
ESP_LOGW(TAG, "Read input registers");
}
exception = mbs_obj->func_handlers[i].handler(inst, mbs_obj->frame, &mbs_obj->length);
break;
}
}
// If the request was not sent to the broadcast address, return a reply.
if ((mbs_obj->rcv_addr != MB_ADDRESS_BROADCAST) || (mbs_obj->cur_mode == MB_TCP)) {
if (exception != MB_EX_NONE) {
// An exception occurred. Build an error frame.
mbs_obj->length = 0;
mbs_obj->frame[mbs_obj->length++] = (uint8_t)(mbs_obj->func_code | MB_FUNC_ERROR);
mbs_obj->frame[mbs_obj->length++] = exception;
}
if ((mbs_obj->cur_mode == MB_ASCII) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS) {
mb_port_tmr_delay(inst->port_obj, MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS);
}
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":MB_SEND"), (void *)mbs_obj->frame, mbs_obj->length, ESP_LOG_WARN);
status = inst->transp_obj->frm_send(inst->transp_obj, mbs_obj->rcv_addr, mbs_obj->frame, mbs_obj->length);
if (status != MB_ENOERR) {
ESP_LOGE(TAG, MB_OBJ_FMT":frame send error. %d", MB_OBJ_PTR(inst), (int)status);
}
}
break;
case EV_FRAME_TRANSMIT:
ESP_LOGD(TAG, MB_OBJ_FMT":EV_FRAME_TRANSMIT", MB_OBJ_PTR(inst));
break;
case EV_FRAME_SENT:
ESP_LOGD(TAG, MB_OBJ_FMT":EV_MASTER_FRAME_SENT", MB_OBJ_PTR(inst));
uint64_t time_div_us = mbs_obj->curr_trans_id ? (event.get_ts - mbs_obj->curr_trans_id) : 0;
mbs_obj->curr_trans_id = 0;
ESP_LOGW(TAG, MB_OBJ_FMT", transaction processing time(us) = %" PRId64, MB_OBJ_PTR(inst), time_div_us);
break;
default:
ESP_LOGD(TAG, MB_OBJ_FMT": Unexpected event triggered 0x%02x.", MB_OBJ_PTR(inst), (int)event.event);
break;
}
} else {
// Something went wrong and task unblocked but there are no any correct events set
ESP_LOGD(TAG, MB_OBJ_FMT": Unexpected event triggered 0x%02x, timeout?", MB_OBJ_PTR(inst), (int)event.event);
status = MB_EILLSTATE;
}
return status;
}

View File

@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <string.h>
/*----------------------- Platform includes --------------------------------*/
#include "spinlock.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "freertos/portmacro.h"
#include "mb_port_types.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define MB_SER_PDU_SIZE_MIN (4)
#define MB_TIMER_TICS_PER_MS (20UL) // Define number of timer reloads per 1 mS
#define MB_TIMER_TICK_TIME_US (1000 / MB_TIMER_TICS_PER_MS) // 50uS = one discreet for timer
#define MB_EVENT_QUEUE_TIMEOUT_MAX_MS (3000)
#define MB_EVENT_QUEUE_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_EVENT_QUEUE_TIMEOUT))
#define MB_EVENT_QUEUE_TIMEOUT_MAX (pdMS_TO_TICKS(MB_EVENT_QUEUE_TIMEOUT_MAX_MS))
int lock_obj(_lock_t *plock);
void unlock_obj(_lock_t *plock);
#define CRITICAL_SECTION_INIT(lock) \
do \
{ \
_lock_init((_lock_t *)&lock); \
} while (0)
#define CRITICAL_SECTION_CLOSE(lock) \
do \
{ \
_lock_close((_lock_t *)&lock); \
} while (0)
#define CRITICAL_SECTION_LOCK(lock) \
do \
{ \
lock_obj((_lock_t *)&lock); \
} while (0)
#define CRITICAL_SECTION_UNLOCK(lock) \
do \
{ \
unlock_obj((_lock_t *)&lock); \
} while (0)
#define CRITICAL_SECTION(lock) for (int st = lock_obj((_lock_t *)&lock); (st > 0); unlock_obj((_lock_t *)&lock), st = -1)
#define SPIN_LOCK_INIT(lock) \
do \
{ \
spinlock_initialize(&lock); \
} while (0)
#define SPIN_LOCK_ENTER(lock) \
do \
{ \
spinlock_acquire(&lock, SPINLOCK_WAIT_FOREVER); \
} while (0)
#define SPIN_LOCK_EXIT(lock) \
do \
{ \
spinlock_release(&lock); \
} while (0)
#define MB_EVENT_REQ_MASK (EventBits_t)(EV_MASTER_PROCESS_SUCCESS | \
EV_MASTER_ERROR_RESPOND_TIMEOUT | \
EV_MASTER_ERROR_RECEIVE_DATA | \
EV_MASTER_ERROR_EXECUTE_FUNCTION)
#define MB_PORT_CHECK_EVENT(event, mask) (event & mask)
#define MB_PORT_CLEAR_EVENT(event, mask) \
do \
{ \
event &= ~mask; \
} while (0)
// concatenation of the two arguments
#define PP_CAT2(_1, _2) PP_CAT_(_1, _2)
#define PP_CAT_(_1, _2) _1##_2
#define PP_VA_NUM_ARGS(...) PP_VA_NUM_ARGS_(__VA_ARGS__, 4, 3, 2, 1)
#define PP_VA_NUM_ARGS_(_1, _2, _3, _4, N, ...) N
// Initialization of event structure using variadic parameters
#define EVENT(...) PP_CAT2(EVENT_, PP_VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
#define EVENT_1(_1) \
(mb_event_t) { .event = _1 }
#define EVENT_2(_1, _2) \
(mb_event_t) { .event = _1, .length = _2 }
#define EVENT_3(_1, _2, _3) \
(mb_event_t) { .event = _1, .length = _2, .pdata = _3 }
#define EVENT_4(_1, _2, _3, _4) \
(mb_event_t) { .event = _1, .length = _2, .pdata = _3, .type = _4 }
typedef struct mb_port_base_t mb_port_base_t;
typedef struct
{
mb_port_base_t *mb_base;
} mb_common_iface_t;
//((mb_port_base_t *)(((mb_common_iface_t *)pctx)->mb_base)->lock);
#define MB_OBJ_GET_LOCK(pctx) (__extension__( \
{ \
assert((pctx)); \
mb_common_iface_t *iface = (mb_common_iface_t *)pctx; \
((_lock_t)((mb_port_base_t *)(iface->mb_base))->lock); \
}))
typedef bool (*mb_port_cb_fp)(void *arg);
//!< port callback table for interrupts
typedef struct
{
mb_port_cb_fp byte_rcvd;
mb_port_cb_fp tx_empty;
mb_port_cb_fp tmr_expired;
} mb_port_cb_t;
typedef struct mb_port_event_t mb_port_event_t;
typedef struct mb_port_timer_t mb_port_timer_t;
typedef struct _obj_descr obj_descr_t;
typedef struct _frame_queue_entry
{
uint16_t tid; /*!< Transaction identifier (TID) for slave */
uint16_t pid; /*!< Protocol ID field of MBAP frame */
uint16_t uid; /*!< Slave unit ID (UID) field for MBAP frame */
uint8_t *pbuf; /*!< Points to the buffer for the frame */
uint16_t len; /*!< Length of the frame in the buffer */
bool check; /*!< Checked flag */
} frame_entry_t;
struct mb_port_base_t
{
obj_descr_t descr;
_lock_t lock;
mb_port_cb_t cb; //!< Port callbacks.
void *arg; //!< CB arg pointer.
mb_port_event_t *event_obj;
mb_port_timer_t *timer_obj;
};
// Port event functions
mb_err_enum_t mb_port_evt_create(mb_port_base_t *port_obj);
bool mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
bool mb_port_evt_get(mb_port_base_t *inst, mb_event_t *event);
bool mb_port_evt_res_take(mb_port_base_t *inst, uint32_t timeout);
void mb_port_evt_res_release(mb_port_base_t *inst);
void mb_port_evt_set_resp_flag(mb_port_base_t *inst, mb_event_enum_t event_mask);
void mb_port_evt_set_err_type(mb_port_base_t *inst, mb_err_event_t event);
mb_err_event_t mb_port_evt_get_err_type(mb_port_base_t *inst);
void mb_port_evt_delete(mb_port_base_t *inst);
mb_err_enum_t mb_port_evt_wait_req_finish(mb_port_base_t *inst);
// Port timer functions
mb_err_enum_t mb_port_tmr_create(mb_port_base_t *inst, uint16_t t35_timer_ticks);
void mb_port_tmr_disable(mb_port_base_t *inst);
void mb_port_tmr_enable(mb_port_base_t *inst);
void mb_port_tmr_respond_timeout_enable(mb_port_base_t *inst);
void mb_port_tmr_convert_delay_enable(mb_port_base_t *inst);
void mb_port_set_cur_tmr_mode(mb_port_base_t *inst, mb_tmr_mode_enum_t tmr_mode);
mb_tmr_mode_enum_t mb_port_get_cur_tmr_mode(mb_port_base_t *inst);
void mb_port_tmr_set_response_time(mb_port_base_t *inst, uint32_t resp_time_ms);
uint32_t mb_port_tmr_get_response_time_ms(mb_port_base_t *inst);
void mb_port_tmr_delay(mb_port_base_t *inst, uint16_t timeout_ms);
void mb_port_tmr_delete(mb_port_base_t *inst);
// Common functions to track instance descriptors
void mb_port_set_inst_counter(uint32_t inst_counter);
uint32_t mb_port_get_inst_counter();
uint32_t mb_port_get_inst_counter_inc();
uint32_t mb_port_get_inst_counter_dec();
// Common queue functions
QueueHandle_t queue_create(int queue_size);
void queue_delete(QueueHandle_t queue);
void queue_flush(QueueHandle_t queue);
bool queue_is_empty(QueueHandle_t queue);
esp_err_t queue_push(QueueHandle_t queue, void *pbuf, size_t len, frame_entry_t *pframe);
ssize_t queue_pop(QueueHandle_t queue, void *pbuf, size_t len, frame_entry_t *pframe);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,209 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include <stdbool.h>
#include <string.h>
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "sdkconfig.h"
#include "port_common.h"
#include "mb_common.h"
static const char *TAG = "mb_port.event";
struct mb_port_event_t
{
volatile mb_err_event_t curr_err_type;
SemaphoreHandle_t resource_hdl;
EventGroupHandle_t event_group_hdl;
QueueHandle_t event_hdl;
uint64_t curr_trans_id;
};
mb_err_enum_t mb_port_evt_create(mb_port_base_t *inst)
{
mb_port_event_t *pevent = NULL;
mb_err_enum_t ret = MB_EILLSTATE;
pevent = (mb_port_event_t *)calloc(1, sizeof(mb_port_event_t));
MB_RETURN_ON_FALSE((pevent && inst), MB_EILLSTATE, TAG, "mb event creation error.");
// Create modbus semaphore (mb resource).
pevent->resource_hdl = xSemaphoreCreateBinary();
MB_GOTO_ON_FALSE((pevent->resource_hdl), MB_EILLSTATE, error, TAG,
"%s, mb resource create failure.", inst->descr.parent_name);
pevent->event_group_hdl = xEventGroupCreate();
MB_GOTO_ON_FALSE((pevent->event_group_hdl), MB_EILLSTATE, error, TAG,
"%s, event group create error.", inst->descr.parent_name);
pevent->event_hdl = xQueueCreate(MB_EVENT_QUEUE_SIZE, sizeof(mb_event_t));
MB_GOTO_ON_FALSE((pevent->event_hdl), MB_EILLSTATE, error, TAG, "%s, event queue create error.", inst->descr.parent_name);
vQueueAddToRegistry(pevent->event_hdl, TAG);
inst->event_obj = pevent;
pevent->curr_err_type = EV_ERROR_INIT;
ESP_LOGD(TAG, "initialized object @%p", pevent);
return MB_ENOERR;
error:
if(pevent->event_hdl) {
vQueueDelete(pevent->event_hdl);
pevent->event_hdl = NULL;
}
if (pevent->event_group_hdl) {
vEventGroupDelete(pevent->event_group_hdl);
pevent->event_group_hdl = NULL;
}
if (pevent->resource_hdl) {
vSemaphoreDelete(pevent->resource_hdl);
pevent->resource_hdl = NULL;
}
free(inst->event_obj);
inst->event_obj = NULL;
return ret;
}
inline void mb_port_evt_set_err_type(mb_port_base_t *inst, mb_err_event_t event)
{
atomic_store(&(inst->event_obj->curr_err_type), event);
}
inline mb_err_event_t mb_port_evt_get_err_type(mb_port_base_t *inst)
{
return atomic_load(&inst->event_obj->curr_err_type);
}
uint64_t mb_port_get_trans_id(mb_port_base_t *inst)
{
return atomic_load(&(inst->event_obj->curr_trans_id));
}
bool mb_port_evt_post(mb_port_base_t *inst, mb_event_t event)
{
if (!inst || !inst->event_obj || !inst->event_obj->event_hdl) {
ESP_LOGE(TAG, "Wrong event handle %d %p %s.", (int)(event.event), inst, inst->descr.parent_name);
}
BaseType_t result = pdFALSE;
mb_event_t temp_event;
temp_event = event;
temp_event.post_ts = esp_timer_get_time();
if (event.event & EV_TRANS_START) {
atomic_store(&(inst->event_obj->curr_trans_id), temp_event.post_ts);
}
temp_event.event = (event.event & ~EV_TRANS_START);
if (xPortInIsrContext()) {
BaseType_t high_prio_task_woken = pdFALSE;
result = xQueueSendFromISR(inst->event_obj->event_hdl,
(const void*)&temp_event, &high_prio_task_woken);
// Was the message posted successfully?
if (result == pdPASS) {
// If high_prio_task_woken is now set to pdTRUE
// then a context switch should be requested.
if (high_prio_task_woken) {
portYIELD_FROM_ISR();
}
return true;
} else {
ESP_EARLY_LOGV(TAG, "%s, post message %x failure .", inst->descr.parent_name, temp_event.event);
return false;
}
if (high_prio_task_woken) {
portYIELD_FROM_ISR();
}
} else {
result = xQueueSend(inst->event_obj->event_hdl, (const void*)&temp_event, MB_EVENT_QUEUE_TIMEOUT_MAX);
if (result != pdTRUE) {
ESP_LOGE(TAG, "%s, post message failure.", inst->descr.parent_name);
return false;
}
}
return true;
}
bool mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent)
{
assert(inst->event_obj->event_hdl);
bool event_happened = false;
if (xQueueReceive(inst->event_obj->event_hdl, pevent, MB_EVENT_QUEUE_TIMEOUT_MAX) == pdTRUE) {
pevent->trans_id = atomic_load(&inst->event_obj->curr_trans_id);
pevent->get_ts = esp_timer_get_time();
event_happened = true;
} else {
ESP_LOGD(TAG, "%s, get event timeout.", inst->descr.parent_name);
}
return event_happened;
}
bool mb_port_evt_res_take(mb_port_base_t *inst, uint32_t timeout)
{
BaseType_t status = pdTRUE;
status = xSemaphoreTake(inst->event_obj->resource_hdl, timeout);
ESP_LOGW(TAG, "%s, mb take resource, (%" PRIu32 " ticks).", inst->descr.parent_name, timeout);
return status;
}
void mb_port_evt_res_release(mb_port_base_t *inst)
{
BaseType_t status = pdFALSE;
status = xSemaphoreGive(inst->event_obj->resource_hdl);
if (status != pdTRUE) {
ESP_LOGD(TAG, "%s, mb resource release.", inst->descr.parent_name);
}
}
void mb_port_evt_set_resp_flag(mb_port_base_t *inst, mb_event_enum_t event_mask)
{
(void)xEventGroupSetBits(inst->event_obj->event_group_hdl, event_mask);
}
mb_err_enum_t mb_port_evt_wait_req_finish(mb_port_base_t *inst)
{
mb_err_enum_t err_status = MB_ENOERR;
mb_event_enum_t rcv_event;
EventBits_t bits = xEventGroupWaitBits(inst->event_obj->event_group_hdl, // The event group being tested.
MB_EVENT_REQ_MASK, // The bits within the event group to wait for.
pdTRUE, // Masked bits should be cleared before returning.
pdFALSE, // Don't wait for both bits, either bit will do.
MB_EVENT_QUEUE_TIMEOUT_MAX); // Wait forever for either bit to be set.
rcv_event = (mb_event_enum_t)(bits);
if (rcv_event) {
ESP_LOGD(TAG, "%s, %s: returned event = 0x%x", inst->descr.parent_name, __func__, (int)rcv_event);
if (!(rcv_event & MB_EVENT_REQ_MASK)) {
// if we wait for certain event bits but get from poll subset
ESP_LOGE(TAG, "%s, %s: incorrect event set = 0x%x", inst->descr.parent_name, __func__, (int)rcv_event);
}
if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_PROCESS_SUCCESS)) {
err_status = MB_ENOERR;
} else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_RESPOND_TIMEOUT)) {
err_status = MB_ETIMEDOUT;
} else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_RECEIVE_DATA)) {
err_status = MB_ERECVDATA;
} else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_EXECUTE_FUNCTION)) {
err_status = MB_EILLFUNC;
}
} else {
ESP_LOGE(TAG, "%s, %s: incorrect event or timeout, rcv_event = 0x%x", inst->descr.parent_name, __func__, (int)bits);
err_status = MB_ETIMEDOUT;
}
return err_status;
}
void mb_port_evt_delete(mb_port_base_t *inst)
{
if (inst->event_obj->resource_hdl) {
vSemaphoreDelete(inst->event_obj->resource_hdl);
}
if (inst->event_obj->event_group_hdl) {
vEventGroupDelete(inst->event_obj->event_group_hdl);
}
if(inst->event_obj->event_hdl) {
vQueueDelete(inst->event_obj->event_hdl);
inst->event_obj->event_hdl = NULL;
}
free(inst->event_obj);
inst->event_obj = NULL;
}

View File

@@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "sys/lock.h"
#include "port_common.h"
/* ----------------------- Variables ----------------------------------------*/
static uint32_t inst_counter = 0;
/* ----------------------- Start implementation -----------------------------*/
int lock_obj(_lock_t *plock)
{
_lock_acquire(plock);
return 1;
}
void unlock_obj(_lock_t *plock)
{
_lock_release(plock);
}
__attribute__((unused))
void mb_port_set_inst_counter(uint32_t counter)
{
atomic_store(&inst_counter, counter);
}
__attribute__((unused))
uint32_t mb_port_get_inst_counter()
{
return atomic_load(&inst_counter);
}
uint32_t mb_port_get_inst_counter_inc()
{
uint32_t counter = atomic_load(&inst_counter);
atomic_store(&inst_counter, (counter + 1));
return counter;
}
uint32_t mb_port_get_inst_counter_dec()
{
uint32_t counter = atomic_load(&inst_counter);
atomic_store(&inst_counter, (counter - 1));
return counter;
}
QueueHandle_t queue_create(int queue_size)
{
return xQueueCreate(queue_size, sizeof(frame_entry_t));
}
void queue_delete(QueueHandle_t queue)
{
vQueueDelete(queue);
}
esp_err_t queue_push(QueueHandle_t queue, void *pbuf, size_t len, frame_entry_t *pframe)
{
frame_entry_t frame_info = {0};
if (!queue || !pbuf || (len <= 0)) {
return -1;
}
if (pframe) {
frame_info = *pframe;
}
frame_info.pbuf = calloc(1, len + 1);
if (!frame_info.pbuf) {
return ESP_ERR_NO_MEM;
}
frame_info.len = len;
memcpy(frame_info.pbuf, pbuf, len);
// try send to queue and check if the queue is full
if (xQueueSend(queue, &frame_info, portMAX_DELAY) != pdTRUE) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
ssize_t queue_pop(QueueHandle_t queue, void *pbuf, size_t len, frame_entry_t *pframe)
{
TickType_t timeout = portMAX_DELAY;
frame_entry_t frame_info = {0};
if (xQueueReceive(queue, &frame_info, timeout) == pdTRUE) {
if (pframe) {
*pframe = frame_info;
}
if (len > frame_info.len) {
len = frame_info.len;
}
// if the input buffer pointer is defined copy the data and free
if (frame_info.pbuf && pbuf) {
memcpy(pbuf, frame_info.pbuf, len);
free(frame_info.pbuf);
}
} else {
goto err;
}
return len;
err:
return -1;
}
bool queue_is_empty(QueueHandle_t queue)
{
return (uxQueueMessagesWaiting(queue) == 0);
}
void queue_flush(QueueHandle_t queue)
{
frame_entry_t frame_info;
while (xQueueReceive(queue, &frame_info, 0) == pdTRUE) {
if ((frame_info.len > 0) && frame_info.pbuf) {
free(frame_info.pbuf);
}
}
}

View File

@@ -0,0 +1,196 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*----------------------- Platform includes --------------------------------*/
#include "esp_idf_version.h"
#include "esp_attr.h"
#if __has_include("driver/gptimer.h")
#include "driver/gptimer.h"
#else
#include "driver/timer.h"
#endif
#include "esp_timer.h"
#include "esp_log.h"
#include "port_common.h"
#include "mb_types.h"
#include "mb_config.h"
#include "mb_common.h"
/* ----------------------- Defines ----------------------------------------*/
struct mb_port_timer_t
{
spinlock_t spin_lock;
esp_timer_handle_t timer_handle;
uint16_t t35_ticks;
uint32_t response_time_ms;
bool timer_state;
uint16_t timer_mode;
};
/* ----------------------- Static variables ---------------------------------*/
static const char *TAG = "mb_port.timer";
/* ----------------------- Start implementation -----------------------------*/
mb_tmr_mode_enum_t mb_port_get_cur_tmr_mode(mb_port_base_t *inst);
static void IRAM_ATTR timer_alarm_cb(void *param)
{
mb_port_base_t *inst = (mb_port_base_t *)param;
if (inst->cb.tmr_expired && inst->arg) {
inst->cb.tmr_expired(inst->arg); // Timer expired callback function
}
inst->timer_obj->timer_state = true;
ESP_EARLY_LOGD(TAG, "timer mode: (%d) triggered", mb_port_get_cur_tmr_mode(inst));
}
mb_err_enum_t mb_port_tmr_create(mb_port_base_t *inst, uint16_t t35_timer_ticks)
{
MB_RETURN_ON_FALSE((t35_timer_ticks > 0), MB_EILLSTATE, TAG,
"modbus timeout discreet is incorrect.");
// MB_RETURN_ON_FALSE((inst && !inst->timer_obj), MB_EILLSTATE, TAG,
// "modbus timer is already created.");
mb_err_enum_t ret = MB_EILLSTATE;
inst->timer_obj = (mb_port_timer_t *)calloc(1, sizeof(mb_port_timer_t));
MB_GOTO_ON_FALSE((inst && inst->timer_obj), MB_EILLSTATE, error, TAG, "mb timer allocation error.");
SPIN_LOCK_INIT(inst->timer_obj->spin_lock);
inst->timer_obj->timer_handle = NULL;
// Set default response time according to kconfig
inst->timer_obj->response_time_ms = MB_MASTER_TIMEOUT_MS_RESPOND;
// Save timer reload value for Modbus T35 period
inst->timer_obj->t35_ticks = t35_timer_ticks;
esp_timer_create_args_t timer_conf = {
.callback = timer_alarm_cb,
.arg = inst,
#if (MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD && MB_TIMER_USE_ISR_DISPATCH_METHOD)
.dispatch_method = ESP_TIMER_ISR,
#else
.dispatch_method = ESP_TIMER_TASK,
#endif
.name = "MB_T35timer"
};
// Create Modbus timer
esp_err_t err = esp_timer_create(&timer_conf, &(inst->timer_obj->timer_handle));
MB_GOTO_ON_FALSE((err == ESP_OK), MB_EILLSTATE, error, TAG, "mb timer creation error.");
ESP_LOGD(TAG, "initialized %s object @%p", TAG, inst->timer_obj);
return MB_ENOERR;
error:
if (inst && inst->timer_obj && inst->timer_obj->timer_handle)
{
esp_timer_delete(inst->timer_obj->timer_handle);
}
free(inst->timer_obj);
inst->timer_obj = NULL;
return ret;
}
void mb_port_tmr_delete(mb_port_base_t *inst)
{
// Delete active timer
if (inst->timer_obj)
{
if (inst->timer_obj->timer_handle)
{
esp_timer_stop(inst->timer_obj->timer_handle);
esp_timer_delete(inst->timer_obj->timer_handle);
}
free(inst->timer_obj);
inst->timer_obj = NULL;
}
}
void mb_port_tmr_us(mb_port_base_t *inst, uint64_t timeout_us)
{
MB_RETURN_ON_FALSE((inst && inst->timer_obj->timer_handle), ;, TAG, "timer is not initialized.");
MB_RETURN_ON_FALSE((timeout_us > 0), ;, TAG,
"%s, incorrect tick value for timer = (%" PRId64 ").", inst->descr.parent_name, timeout_us);
esp_timer_stop(inst->timer_obj->timer_handle);
esp_timer_start_once(inst->timer_obj->timer_handle, timeout_us);
SPIN_LOCK_ENTER(inst->timer_obj->spin_lock);
inst->timer_obj->timer_state = false;
SPIN_LOCK_EXIT(inst->timer_obj->spin_lock);
}
inline void mb_port_set_cur_tmr_mode(mb_port_base_t *inst, mb_tmr_mode_enum_t tmr_mode)
{
SPIN_LOCK_ENTER(inst->timer_obj->spin_lock);
inst->timer_obj->timer_mode = tmr_mode;
SPIN_LOCK_EXIT(inst->timer_obj->spin_lock);
}
inline mb_tmr_mode_enum_t mb_port_get_cur_tmr_mode(mb_port_base_t *inst)
{
return inst->timer_obj->timer_mode;
}
void mb_port_tmr_enable(mb_port_base_t *inst)
{
uint64_t tout_us = (inst->timer_obj->t35_ticks * MB_TIMER_TICK_TIME_US);
// Set current timer mode, don't change it.
mb_port_set_cur_tmr_mode(inst, MB_TMODE_T35);
// Set timer alarm
mb_port_tmr_us(inst, tout_us);
ESP_LOGW(TAG, "%s, start timer (%" PRIu64 ").", inst->descr.parent_name, tout_us);
}
void mb_port_tmr_convert_delay_enable(mb_port_base_t *inst)
{
// Covert time in milliseconds into ticks
uint64_t tout_us = (MB_MASTER_DELAY_MS_CONVERT * 1000);
// Set current timer mode
mb_port_set_cur_tmr_mode(inst, MB_TMODE_CONVERT_DELAY);
ESP_LOGD(TAG, "%s, convert delay enable.", inst->descr.parent_name);
mb_port_tmr_us(inst, tout_us);
}
void mb_port_tmr_respond_timeout_enable(mb_port_base_t *inst)
{
uint64_t tout_us = (inst->timer_obj->response_time_ms * 1000);
mb_port_set_cur_tmr_mode(inst, MB_TMODE_RESPOND_TIMEOUT);
ESP_LOGW(TAG, "%s, respond enable timeout (%d).",
inst->descr.parent_name, (int)inst->timer_obj->response_time_ms);
mb_port_tmr_us(inst, tout_us);
}
void mb_port_tmr_delay(mb_port_base_t *inst, uint16_t timeout_ms)
{
uint64_t tout_us = (timeout_ms * 1000);
mb_port_tmr_us(inst, tout_us);
}
void mb_port_tmr_disable(mb_port_base_t *inst)
{
// Disable timer alarm
esp_err_t err = esp_timer_stop(inst->timer_obj->timer_handle);
if (err != ESP_OK)
{
if (!esp_timer_is_active(inst->timer_obj->timer_handle))
{
ESP_EARLY_LOGD(TAG, "%s, timer stop, returns %d.", inst->descr.parent_name, (int)err);
}
}
}
void mb_port_tmr_set_response_time(mb_port_base_t *inst, uint32_t resp_time_ms)
{
SPIN_LOCK_ENTER(inst->timer_obj->spin_lock);
inst->timer_obj->response_time_ms = resp_time_ms;
SPIN_LOCK_EXIT(inst->timer_obj->spin_lock);
}
uint32_t mb_port_tmr_get_response_time_ms(mb_port_base_t *inst)
{
return inst->timer_obj->response_time_ms;
}

View File

@@ -0,0 +1,349 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include "esp_timer.h"
#include "mb_common.h"
#include "port_common.h"
#include "mb_config.h"
#include "port_serial_common.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_SERIAL_RX_SEMA_TOUT_MS (1000)
#define MB_SERIAL_RX_SEMA_TOUT (pdMS_TO_TICKS(MB_SERIAL_RX_SEMA_TOUT_MS))
#define MB_SERIAL_RX_FLUSH_RETRY (2)
#define MB_QUEUE_LENGTH (20)
#define MB_SERIAL_TOUT (3)
#define MB_SERIAL_TX_TOUT_TICKS (pdMS_TO_TICKS(400))
#define MB_SERIAL_TASK_STACK_SIZE (CONFIG_FMB_PORT_TASK_STACK_SIZE)
#define MB_SERIAL_RX_TOUT_TICKS (pdMS_TO_TICKS(100))
#define MB_SERIAL_MIN_PATTERN_INTERVAL (9)
#define MB_SERIAL_MIN_POST_IDLE (0)
#define MB_SERIAL_MIN_PRE_IDLE (0)
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct
{
mb_port_base_t base;
// serial communication properties
mb_serial_opts_t ser_opts;
bool rx_state_en;
bool tx_state_en;
uint16_t recv_length;
uint64_t send_time_stamp;
uint64_t recv_time_stamp;
uint32_t flags;
bool enabled;
QueueHandle_t uart_queue; // A queue to handle UART event.
TaskHandle_t task_handle; // UART task to handle UART event.
SemaphoreHandle_t bus_sema_handle; // Rx blocking semaphore handle
} mb_ser_port_t;
/* ----------------------- Static variables & functions ----------------------*/
static const char *TAG = "mb_port.serial";
static bool mb_port_ser_bus_sema_init(mb_port_base_t *inst)
{
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
port_obj->bus_sema_handle = xSemaphoreCreateBinary();
MB_RETURN_ON_FALSE((port_obj->bus_sema_handle), false , TAG,
"%s: RX semaphore create failure.", inst->descr.parent_name);
return true;
}
static void mb_port_ser_bus_sema_close(mb_port_base_t *inst)
{
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
if (port_obj->bus_sema_handle) {
vSemaphoreDelete(port_obj->bus_sema_handle);
port_obj->bus_sema_handle = NULL;
}
}
static bool mb_port_ser_bus_sema_take(mb_port_base_t *inst, uint32_t tm_ticks)
{
BaseType_t status = pdTRUE;
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
status = xSemaphoreTake(port_obj->bus_sema_handle, tm_ticks );
MB_RETURN_ON_FALSE((status == pdTRUE), false , TAG,
"%s, rx semaphore take failure.", inst->descr.parent_name);
ESP_LOGV(TAG, "%s: take RX semaphore (%" PRIu32" ticks).", inst->descr.parent_name, tm_ticks);
return true;
}
static void mb_port_ser_bus_sema_release(mb_port_base_t *inst)
{
BaseType_t status = pdFALSE;
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
status = xSemaphoreGive(port_obj->bus_sema_handle);
if (status != pdTRUE) {
ESP_LOGD(TAG, "%s, rx semaphore is free.", inst->descr.parent_name);
}
}
static bool mb_port_ser_bus_sema_is_busy(mb_port_base_t *inst)
{
BaseType_t status = pdFALSE;
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
status = (uxSemaphoreGetCount(port_obj->bus_sema_handle) == 0) ? true : false;
return status;
}
static void mb_port_ser_rx_flush(mb_port_base_t *inst)
{
size_t size = 1;
esp_err_t err = ESP_OK;
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
for (int cnt = 0; (cnt < MB_SERIAL_RX_FLUSH_RETRY) && size; cnt++) {
err = uart_get_buffered_data_len(port_obj->ser_opts.port, &size);
MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG,
"%s, mb flush serial fail, error = 0x%x.", inst->descr.parent_name, (int)err);
BaseType_t status = xQueueReset(port_obj->uart_queue);
if (status) {
err = uart_flush_input(port_obj->ser_opts.port);
MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG,
"%s, mb flush serial fail, error = 0x%x.", inst->descr.parent_name, (int)err);
}
}
}
void mb_port_ser_enable(mb_port_base_t *inst)
{
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
CRITICAL_SECTION (port_obj->base.lock) {
atomic_store(&(port_obj->enabled), true);
mb_port_ser_bus_sema_release(inst);
ESP_LOGW(TAG, "%s, resume port.", port_obj->base.descr.parent_name);
// Resume receiver task from known position
vTaskResume(port_obj->task_handle);
}
}
void mb_port_ser_disable(mb_port_base_t *inst)
{
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
CRITICAL_SECTION (port_obj->base.lock) {
// Suspend port task by itself
atomic_store(&(port_obj->enabled), false);
ESP_LOGW(TAG, "%s, suspend port.", port_obj->base.descr.parent_name);
}
}
// UART receive event task
static void mb_port_ser_task(void *p_args)
{
mb_ser_port_t *port_obj = __containerof(p_args, mb_ser_port_t, base);
uart_event_t event;
MB_RETURN_ON_FALSE(port_obj, ;, TAG, "%s, get serial instance fail.", port_obj->base.descr.parent_name);
(void)mb_port_ser_rx_flush(&port_obj->base);
while(1) {
// Workaround to suspend task from known place to avoid dead lock when resume
if (!atomic_load(&(port_obj->enabled))) {
ESP_LOGI(TAG, "%s, suspend port from task.", port_obj->base.descr.parent_name);
vTaskSuspend(NULL);
}
if (xQueueReceive(port_obj->uart_queue, (void *)&event, MB_SERIAL_RX_TOUT_TICKS)) {
ESP_LOGD(TAG, "%s, UART[%d] event:", port_obj->base.descr.parent_name, port_obj->ser_opts.port);
switch(event.type) {
case UART_DATA:
ESP_LOGW(TAG, "%s, data event, len: %d.", port_obj->base.descr.parent_name, (int)event.size);
// This flag set in the event means that no more
// data received during configured timeout and UART TOUT feature is triggered
if (event.timeout_flag) {
// If bus is busy or fragmented data is received, then flush buffer
if (mb_port_ser_bus_sema_is_busy(&port_obj->base)) {
mb_port_ser_rx_flush(&port_obj->base);
break;
}
uart_get_buffered_data_len(port_obj->ser_opts.port, (unsigned int*)&event.size);
port_obj->recv_length = (event.size < MB_BUFFER_SIZE) ? event.size : MB_BUFFER_SIZE;
if (event.size <= MB_SER_PDU_SIZE_MIN) {
ESP_LOGW(TAG, "%s, drop short packet %d byte(s)", port_obj->base.descr.parent_name, (int)event.size);
(void)mb_port_ser_rx_flush(&port_obj->base);
break;
}
// New frame is received, send an event to main FSM to read it into receiver buffer
mb_port_evt_post(&port_obj->base, EVENT(EV_FRAME_RECEIVED, port_obj->recv_length, NULL, 0));
ESP_LOGD(TAG, "%s, frame %d bytes is ready.", port_obj->base.descr.parent_name, (int)port_obj->recv_length);
}
break;
//Event of HW FIFO overflow detected
case UART_FIFO_OVF:
ESP_LOGD(TAG, "%s, hw fifo overflow.", port_obj->base.descr.parent_name);
xQueueReset(port_obj->uart_queue);
break;
//Event of UART ring buffer full
case UART_BUFFER_FULL:
ESP_LOGD(TAG, "%s, ring buffer full.", port_obj->base.descr.parent_name);
(void)mb_port_ser_rx_flush(&port_obj->base);
break;
//Event of UART RX break detected
case UART_BREAK:
ESP_LOGD(TAG, "%s, uart rx break.", port_obj->base.descr.parent_name);
break;
//Event of UART parity check error
case UART_PARITY_ERR:
ESP_LOGW(TAG, "%s, uart parity error.", port_obj->base.descr.parent_name);
(void)mb_port_ser_rx_flush(&port_obj->base);
break;
//Event of UART frame error
case UART_FRAME_ERR:
ESP_LOGW(TAG, "%s, uart frame error.", port_obj->base.descr.parent_name);
(void)mb_port_ser_rx_flush(&port_obj->base);
break;
default:
ESP_LOGD(TAG, "%s, uart event type: %d.", port_obj->base.descr.parent_name, (int)event.type);
break;
}
}
}
vTaskDelete(NULL);
}
mb_err_enum_t mb_port_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj)
{
mb_ser_port_t *pserial = NULL;
esp_err_t err = ESP_OK;
__attribute__((unused)) mb_err_enum_t ret = MB_EILLSTATE;
pserial = (mb_ser_port_t*)calloc(1, sizeof(mb_ser_port_t));
MB_RETURN_ON_FALSE((pserial && in_out_obj), MB_EILLSTATE, TAG, "mb serial port creation error.");
CRITICAL_SECTION_INIT(pserial->base.lock);
pserial->base.descr = ((mb_port_base_t*)*in_out_obj)->descr;
ser_opts->data_bits = ((ser_opts->data_bits > UART_DATA_5_BITS)
&& (ser_opts->data_bits < UART_DATA_BITS_MAX))
? ser_opts->data_bits : UART_DATA_8_BITS;
// Keep the UART communication options
pserial->ser_opts = *ser_opts;
// Configure serial communication parameters
uart_config_t uart_cfg = {
.baud_rate = ser_opts->baudrate,
.data_bits = ser_opts->data_bits,
.parity = ser_opts->parity,
.stop_bits = ser_opts->stop_bits,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 2,
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
.source_clk = UART_SCLK_DEFAULT,
#else
.source_clk = UART_SCLK_APB,
#endif
};
// Set UART config
err = uart_param_config(pserial->ser_opts.port, &uart_cfg);
MB_GOTO_ON_FALSE((err == ESP_OK), MB_EILLSTATE, error, TAG,
"%s, mb config failure, uart_param_config() returned (0x%x).", pserial->base.descr.parent_name, (int)err);
// Install UART driver, and get the queue.
err = uart_driver_install(pserial->ser_opts.port, MB_BUFFER_SIZE, MB_BUFFER_SIZE,
MB_QUEUE_LENGTH, &pserial->uart_queue, MB_PORT_SERIAL_ISR_FLAG);
MB_GOTO_ON_FALSE((err == ESP_OK), MB_EILLSTATE, error, TAG,
"%s, mb serial driver failure, retuned (0x%x).", pserial->base.descr.parent_name, (int)err);
err = uart_set_rx_timeout(pserial->ser_opts.port, MB_SERIAL_TOUT);
MB_GOTO_ON_FALSE((err == ESP_OK), MB_EILLSTATE, error, TAG,
"%s, mb serial set rx timeout failure, returned (0x%x).", pserial->base.descr.parent_name, (int)err);
// Set always timeout flag to trigger timeout interrupt even after rx fifo full
uart_set_always_rx_timeout(pserial->ser_opts.port, true);
MB_GOTO_ON_FALSE((mb_port_ser_bus_sema_init(&pserial->base)), MB_EILLSTATE, error, TAG,
"%s, mb serial bus semaphore create fail.", pserial->base.descr.parent_name);
// Suspend task on start and then resume when initialization is completed
atomic_store(&(pserial->enabled), false);
// Create a task to handle UART events
BaseType_t status = xTaskCreatePinnedToCore(mb_port_ser_task, "port_serial_task",
MB_SERIAL_TASK_STACK_SIZE,
&pserial->base, CONFIG_FMB_PORT_TASK_PRIO,
&pserial->task_handle, CONFIG_FMB_PORT_TASK_AFFINITY);
// Force exit from function with failure
MB_GOTO_ON_FALSE((status == pdPASS), MB_EILLSTATE, error, TAG,
"%s, mb stack serial task creation error, returned (0x%x).",
pserial->base.descr.parent_name, (int)status);
*in_out_obj = &(pserial->base);
ESP_LOGD(TAG, "created object @%p", pserial);
return MB_ENOERR;
error:
if (pserial) {
if (pserial->task_handle) {
vTaskDelete(pserial->task_handle);
}
uart_driver_delete(pserial->ser_opts.port);
CRITICAL_SECTION_CLOSE(pserial->base.lock);
mb_port_ser_bus_sema_close(&pserial->base);
}
free(pserial);
return MB_EILLSTATE;
}
void mb_port_ser_delete(mb_port_base_t *inst)
{
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
vTaskDelete(port_obj->task_handle);
ESP_ERROR_CHECK(uart_driver_delete(port_obj->ser_opts.port));
mb_port_ser_bus_sema_close(inst);
CRITICAL_SECTION_CLOSE(inst->lock);
free(port_obj);
}
bool mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length)
{
MB_RETURN_ON_FALSE((pp_ser_frame && p_ser_length), false, TAG, "mb serial get buffer failure.");
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
uint16_t counter = *p_ser_length ? *p_ser_length : port_obj->recv_length;
bool status = false;
status = mb_port_ser_bus_sema_take(inst, pdMS_TO_TICKS(mb_port_tmr_get_response_time_ms(inst)));
if (status && counter && *pp_ser_frame && atomic_load(&(port_obj->enabled))) {
// Read frame data from the ringbuffer of receiver
counter = uart_read_bytes(port_obj->ser_opts.port, (uint8_t *)*pp_ser_frame,
counter, MB_SERIAL_RX_TOUT_TICKS);
// Stop timer because the new data is received
mb_port_tmr_disable(inst);
// Store the timestamp of received frame
port_obj->recv_time_stamp = esp_timer_get_time();
ESP_LOGW(TAG, "%s, received data: %d bytes.", inst->descr.parent_name, (int)counter);
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":PORT_RECV"), (void *)*pp_ser_frame, counter, ESP_LOG_WARN);
int64_t time_delta = (port_obj->recv_time_stamp > port_obj->send_time_stamp) ?
(port_obj->recv_time_stamp - port_obj->send_time_stamp) :
(port_obj->send_time_stamp - port_obj->recv_time_stamp);
ESP_LOGW(TAG, "%s, serial processing time[us] = %" PRId64, inst->descr.parent_name, time_delta);
status = true;
*p_ser_length = counter;
} else {
ESP_LOGE(TAG, "%s: junk data (%d bytes) received. ", inst->descr.parent_name, (int)counter);
}
*p_ser_length = counter;
mb_port_ser_bus_sema_release(inst);
return status;
}
bool mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length)
{
bool res = false;
int count = 0;
mb_ser_port_t *port_obj = __containerof(inst, mb_ser_port_t, base);
res = mb_port_ser_bus_sema_take(inst, pdMS_TO_TICKS(mb_port_tmr_get_response_time_ms(inst)));
if (res && p_ser_frame && ser_length && atomic_load(&(port_obj->enabled))) {
// Flush buffer received from previous transaction
mb_port_ser_rx_flush(inst);
count = uart_write_bytes(port_obj->ser_opts.port, p_ser_frame, ser_length);
// Waits while UART sending the packet
esp_err_t status = uart_wait_tx_done(port_obj->ser_opts.port, MB_SERIAL_TX_TOUT_TICKS);
(void)mb_port_evt_post(inst, EVENT(EV_FRAME_SENT));
ESP_LOGD(TAG, "%s, tx buffer sent: (%d) bytes.", inst->descr.parent_name, (int)count);
MB_RETURN_ON_FALSE((status == ESP_OK), false, TAG, "%s, mb serial sent buffer failure.",
inst->descr.parent_name);
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":PORT_SEND"),
(void *)p_ser_frame, ser_length, ESP_LOG_WARN);
port_obj->send_time_stamp = esp_timer_get_time();
} else {
ESP_LOGE(TAG, "%s, send fail state:%d, %p, %u. ", inst->descr.parent_name, (int)port_obj->tx_state_en, p_ser_frame, (unsigned)ser_length);
}
mb_port_ser_bus_sema_release(inst);
return res;
}
#endif

View File

@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <string.h>
#include "mb_config.h"
#include "mb_types.h"
#include "mb_frame.h"
#include "mb_port_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum _mb_comm_mode mb_mode_type_t;
typedef struct mb_port_base_t mb_port_base_t;
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
mb_err_enum_t mb_port_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **port_obj);
bool mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length);
bool mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length);
void mb_port_ser_enable(mb_port_base_t *inst);
void mb_port_ser_disable(mb_port_base_t *inst);
void mb_port_ser_delete(mb_port_base_t *inst);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <string.h>
#include "mb_config.h"
#include "mb_common.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
#define MB_TCP_PORT_MAX_CONN (CONFIG_FMB_TCP_PORT_MAX_CONN)
#define MB_TCP_DEFAULT_PORT (502)
#define MB_FRAME_QUEUE_SZ (10)
#define MB_TCP_CONNECTION_TIMEOUT_MS (20) // connection timeout in mS
#define MB_TCP_RECONNECT_TIMEOUT (5000000) // reconnection timeout in uS
#define MB_EVENT_SEND_RCV_TOUT_MS (500)
#define MB_TCP_MBAP_GET_FIELD(buffer, field) ((uint16_t)((buffer[field] << 8U) | buffer[field + 1]))
#define MB_TCP_MBAP_SET_FIELD(buffer, field, val) { \
buffer[(field)] = (uint8_t)((val) >> 8U); \
buffer[(field) + 1] = (uint8_t)((val) & 0xFF); \
}
#define MB_SLAVE_FMT(fmt) "slave #%d, socket(#%d)(%s)" fmt
mb_err_enum_t mbm_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **port_obj);
void mbm_port_tcp_delete(mb_port_base_t *inst);
void mbm_port_tcp_enable(mb_port_base_t *inst);
void mbm_port_tcp_disable(mb_port_base_t *inst);
bool mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
bool mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
bool mbm_port_tcp_add_slave_info(mb_port_base_t *inst, const uint16_t index, const char *ip_str, uint8_t uid);
mb_err_enum_t mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **port_obj);
void mbs_port_tcp_delete(mb_port_base_t *inst);
void mbs_port_tcp_enable(mb_port_base_t *inst);
void mbs_port_tcp_disable(mb_port_base_t *inst);
bool mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length);
bool mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
#endif
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,296 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
//#include <sys/queue.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_event.h" // for esp event loop
#if __has_include("mdns.h")
#include "mdns.h"
#endif
#include "mb_frame.h"
#include "mb_config.h"
#include "port_tcp_utils.h"
#include "mb_port_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
#define MB_PORT_DEFAULT (502)
#define INVALID_FD (-1)
#define MB_EVENT_TOUT (300 / portTICK_PERIOD_MS)
#define MB_CONN_TICK_TIMEOUT (10 / portTICK_PERIOD_MS)
#define EVENT_HANDLER(handler_name) void handler_name(void *ctx, esp_event_base_t base, int32_t id, void *data)
#define MB_MAX_FDS (MB_TCP_PORT_MAX_CONN)
#define MB_RECONNECT_TIME_MS (1000)
#define MB_RX_QUEUE_MAX_SIZE (CONFIG_FMB_QUEUE_LENGTH)
#define MB_TX_QUEUE_MAX_SIZE (CONFIG_FMB_QUEUE_LENGTH)
#define MB_EVENT_QUEUE_SZ (CONFIG_FMB_QUEUE_LENGTH * MB_TCP_PORT_MAX_CONN)
#define MB_TASK_STACK_SZ (CONFIG_FMB_PORT_TASK_STACK_SIZE)
#define MB_TASK_PRIO (CONFIG_FMB_PORT_TASK_PRIO)
#define MB_PORT_TASK_AFFINITY (CONFIG_FMB_PORT_TASK_AFFINITY)
#define MB_WAIT_DONE_MS (5000)
#define MB_SELECT_WAIT_MS (200)
#define MB_TCP_SEND_TIMEOUT_MS (500)
#define MB_TCP_EVENT_LOOP_TICK_MS (50)
#define MB_DRIVER_CONFIG_DEFAULT { \
.spin_lock = portMUX_INITIALIZER_UNLOCKED, \
.mb_tcp_task_handle = NULL, \
.mb_slave_open_count = 0, \
.curr_slave_index = 0, \
.mb_proto = MB_TCP, \
.network_iface_ptr = NULL, \
.mb_slave_info = NULL, \
.mb_slave_curr_info = NULL, \
.close_done_sema = NULL, \
.max_conn_sd = INVALID_FD, \
.slave_conn_count = 0, \
.event_fd = INVALID_FD, \
}
#define MB_EVENTFD_CONFIG() (esp_vfs_eventfd_config_t) { \
.max_fds = MB_TCP_PORT_MAX_CONN \
};
typedef struct _port_driver port_driver_t;
#define MB_CHECK_FD_RANGE(fd) ((fd < MB_TCP_PORT_MAX_CONN) && (fd >= 0))
#define GET_CONFIG_PTR(ctx) (__extension__( \
{ \
assert(ctx); \
((port_driver_t *)ctx); \
} \
))
#define MB_EVENT_TBL_IT(event) {event, #event}
#define MB_EVENT_BASE(context) (__extension__( \
{ \
port_driver_t *pdrv_ctx = GET_CONFIG_PTR(context); \
(pdrv_ctx->loop_name) ? (esp_event_base_t)(pdrv_ctx->loop_name) : "UNK_BASE"; \
} \
))
#define MB_GET_SLAVE_STATE(pslave) (atomic_load(&((mb_slave_info_t *)pslave)->addr_info.state))
#define MB_SET_SLAVE_STATE(pslave, slave_state) do { \
atomic_store(&(((mb_slave_info_t *)pslave)->addr_info.state), slave_state); \
} while(0)
typedef enum _mb_driver_event {
MB_EVENT_READY = 0x0001,
MB_EVENT_OPEN = 0x0002,
MB_EVENT_RESOLVE = 0x0004,
MB_EVENT_CONNECT = 0x0008,
MB_EVENT_SEND_DATA = 0x0010,
MB_EVENT_RECV_DATA = 0x0020,
MB_EVENT_RECONNECT = 0x0040,
MB_EVENT_CLOSE = 0x0080,
MB_EVENT_TIMEOUT = 0x0100
} mb_driver_event_t;
typedef struct {
mb_driver_event_t event;
const char *msg;
} event_msg_t;
typedef union {
struct {
int32_t event_id; /*!< an event */
int32_t opt_fd; /*!< fd option for an event */
};
uint64_t val;
} mb_event_info_t;
// Post event to event loop and unblocks the select through the eventfd to handle the event loop run,
// So, the eventfd value keeps last event and its fd.
#define DRIVER_SEND_EVENT(ctx, event, fd) (__extension__( \
{ \
port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); \
static mb_event_info_t event_info; \
event_info.event_id = (int32_t)event; \
event_info.opt_fd = fd; \
(write_event((void *)pdrv_ctx, &event_info) > 0) ? event_info.event_id : -1; \
} \
))
typedef struct _mb_slave_info {
int index; /*!< slave information index */
int fd; /*!< slave global file descriptor */
int sock_id; /*!< socket ID of slave */
int error; /*!< socket error */
int recv_err; /*!< socket receive error */
mb_uid_info_t addr_info; /*!< slave address info structure*/
QueueHandle_t rx_queue; /*!< receive response queue */
QueueHandle_t tx_queue; /*!< send request queue */
int64_t send_time; /*!< send request time stamp */
int64_t recv_time; /*!< receive response time stamp */
uint16_t tid_counter; /*!< transaction identifier (TID) for slave */
uint16_t send_counter; /*!< number of packets sent to slave during one session */
uint16_t recv_counter; /*!< number of packets received from slave during one session */
bool is_blocking; /*!< slave blocking bit state saved */
} mb_slave_info_t;
typedef enum _mb_sync_event {
MB_SYNC_EVENT_RECV_OK = 0x0001,
MB_SYNC_EVENT_RECV_FAIL = 0x0002,
MB_SYNC_EVENT_SEND_OK = 0x0003,
MB_SYNC_EVENT_TOUT
} mb_sync_event_t;
typedef enum _mb_status_flags {
MB_FLAG_DISCONNECTED = 0x0001,
MB_FLAG_CONNECTED = 0x0002,
MB_FLAG_SUSPEND = 0x0004,
MB_FLAG_SHUTDOWN = 0x0008
} mb_status_flags_t;
typedef struct _driver_event_cbs {
void (*on_conn_done_cb)(void *);
void *arg;
void (*mb_sync_event_cb)(void *, mb_sync_event_t);
void *port_arg;
} mb_driver_event_cb_t;
/**
* @brief Modbus slave addr list item for the master
*/
// typedef struct mb_uid_entry_s {
// void* pinst;
// mb_uid_info_t addr_info;
// LIST_ENTRY(mb_uid_entry_s) entries; /*!< The slave address entry */
// } mb_uid_entry_t;
/**
* @brief Modbus driver context parameters
*
*/
typedef struct _port_driver {
void *parent; /*!< Parent object */
portMUX_TYPE spin_lock; /*!< Driver spin lock */
_lock_t lock; /*!< Driver semaphore mutex */
bool is_registered; /*!< Driver is active flag */
TaskHandle_t mb_tcp_task_handle; /*!< Master TCP/UDP handling task handle */
mb_comm_mode_t mb_proto; /*!< Master protocol type */
void *network_iface_ptr; /*!< Master netif interface pointer */
mb_slave_info_t **mb_slave_info; /*!< Master information structure for each connected slave */
uint16_t mb_slave_open_count; /*!< Master count of connected slaves */
mb_slave_info_t *mb_slave_curr_info; /*!< Master current slave information */
uint16_t curr_slave_index; /*!< Master current processing slave index */
fd_set open_set; /*!< File descriptor set for opened slaves */
fd_set conn_set; /*!< File descriptor set for connected slaves */
EventGroupHandle_t status_flags_hdl; /*!< Status bits to control nodes states */
int max_conn_sd; /*!< Max file descriptor for connected slaves */
int slave_conn_count; /*!< Number of connected slaves */
SemaphoreHandle_t close_done_sema; /*!< Close and done semaphore */
int event_fd; /*!< eventfd descriptor for modbus event tracking */
esp_event_loop_handle_t event_loop_hdl; /*!< event loop handle */
char *loop_name; /*!< name for event loop used as base */
esp_event_handler_instance_t event_handler; /*!< event handler instance */
mb_driver_event_cb_t event_cbs;
//LIST_HEAD(mb_uid_info_, mb_uid_entry_s) slave_list; /*!< Slave address information list */
uint16_t slave_list_count;
} port_driver_t;
/**
* @brief Register modbus driver
*
* This function must be called prior usage of ESP-MODBUS Interface
*
* @param ctx - pointer to pointer of driver interface structure to be created.
* @param config MODBUS virtual filesystem driver configuration. Default base path /dev/net/modbus/tcp is used when this paramenter is NULL.
* @return esp_err_t
* - ESP_OK on success
*/
esp_err_t mbm_drv_register(port_driver_t **config);
/**
* @brief Unregister modbus driver
*
* @param ctx - pointer to driver interface structure
* @return esp_err_t
* - ESP_OK on success
*/
esp_err_t mbm_drv_unregister(void *ctx);
/**
* @brief Start task of modbus driver
*
* @param ctx - pointer to driver interface structure
* @return esp_err_t
* - ESP_OK on success
*/
esp_err_t mbm_drv_start_task(void *ctx);
/**
* @brief Unregister modbus driver
*
* @param ctx - pointer to driver interface structure
* @return esp_err_t
* - ESP_OK on success
*/
esp_err_t mbm_drv_stop_task(void *ctx);
/**
* @brief get slave information structure from its short slave address
*
* This function must be called after initialization of ESP-MODBUS Interface
*
* @param uid - modbus slave address of the slave
* @return mb_slave_info_t
* - Address of slave info structure on success
* - NULL, if the slave is not found
*/
mb_slave_info_t *mbm_drv_get_slave_info_from_addr(void *ctx, uint8_t uid);
int mbm_drv_open(void *ctx, mb_uid_info_t addr_info, int flags);
ssize_t mbm_drv_write(void *ctx, int fd, const void *data, size_t size);
ssize_t mbm_drv_read(void *ctx, int fd, void *data, size_t size);
int mbm_drv_close(void *ctx, int fd);
int32_t write_event(void *ctx, mb_event_info_t *pevent);
const char *driver_event_to_name_r(mb_driver_event_t event);
void mbm_drv_set_cb(void *ctx, void *conn_cb, void *arg);
mb_status_flags_t mbm_drv_wait_status_flag(void *ctx, mb_status_flags_t mask, uint32_t tout_ms);
EVENT_HANDLER(on_ready);
EVENT_HANDLER(on_open);
EVENT_HANDLER(on_connect);
EVENT_HANDLER(on_resolve);
EVENT_HANDLER(on_send_data);
EVENT_HANDLER(on_recv_data);
EVENT_HANDLER(on_reconnect);
EVENT_HANDLER(on_close);
EVENT_HANDLER(on_timeout);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,259 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <string.h>
#include "port_tcp_common.h"
#include "port_tcp_driver.h"
#include "port_tcp_master.h"
#include "port_tcp_utils.h"
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef struct
{
mb_port_base_t base;
// TCP communication properties
mb_tcp_opts_t tcp_opts;
uint8_t ptemp_buf[MB_TCP_BUFF_MAX_SIZE];
port_driver_t *pdriver;
} mbm_tcp_port_t;
/* ----------------------- Static variables & functions ----------------------*/
static const char *TAG = "mb_port.tcp.master";
static void mbm_port_tcp_sync_event(void *inst, mb_sync_event_t sync_event);
bool mbm_port_timer_expired(void *inst);
extern int port_scan_addr_string(char *buffer, mb_uid_info_t *pslave_info);
mb_err_enum_t mbm_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **port_obj)
{
MB_RETURN_ON_FALSE((port_obj && tcp_opts), MB_EINVAL, TAG, "mb tcp port invalid arguments.");
mbm_tcp_port_t *ptcp = NULL;
esp_err_t err = ESP_OK;
mb_err_enum_t ret = MB_EILLSTATE;
ptcp = (mbm_tcp_port_t*)calloc(1, sizeof(mbm_tcp_port_t));
MB_GOTO_ON_FALSE(ptcp, MB_EILLSTATE, error, TAG, "mb tcp port creation error.");
ptcp->pdriver = NULL;
CRITICAL_SECTION_INIT(ptcp->base.lock);
ptcp->base.descr = ((mb_port_base_t *)*port_obj)->descr;
err = mbm_drv_register(&ptcp->pdriver);
MB_GOTO_ON_FALSE(((err == ESP_OK) && ptcp->pdriver), MB_EILLSTATE, error,
TAG, "mb tcp port driver registration failed.");
ptcp->pdriver->parent = ptcp;
ptcp->pdriver->network_iface_ptr = tcp_opts->ip_netif_ptr;
ptcp->pdriver->mb_proto = tcp_opts->mode;
ptcp->pdriver->event_cbs.mb_sync_event_cb = mbm_port_tcp_sync_event;
ptcp->pdriver->event_cbs.port_arg = (void *)ptcp;
ptcp->base.cb.tmr_expired = mbm_port_timer_expired;
ptcp->base.cb.tx_empty = NULL;
ptcp->base.cb.byte_rcvd = NULL;
ptcp->base.arg = (void *)ptcp;
char **paddr_table = tcp_opts->ip_addr_table;
MB_GOTO_ON_FALSE((paddr_table && *paddr_table), MB_EILLSTATE, error,
TAG, "mb tcp port driver registration failed.");
mb_uid_info_t slave_address_info;
int fd = 0;
// Just for test now
while(*paddr_table) {
int res = port_scan_addr_string((char *)*paddr_table, &slave_address_info);
if (res > 0) {
ESP_LOGW(TAG, "Config: %s, IP: %s, port: %d, slave_addr: %d, ip_ver: %s",
(char *)*paddr_table, slave_address_info.ip_addr_str, slave_address_info.port,
slave_address_info.uid, (slave_address_info.addr_type == MB_IPV4 ? "IPV4" : "IPV6"));
fd = mbm_drv_open(ptcp->pdriver, slave_address_info, 0);
if (fd < 0) {
ESP_LOGE(TAG, "%p, unable to open slave: %s", ptcp->pdriver, slave_address_info.ip_addr_str);
} else {
ESP_LOGW(TAG, "%p, open slave: %d, %s:%d",
ptcp->pdriver, fd, slave_address_info.ip_addr_str, slave_address_info.port);
}
} else {
ESP_LOGE(TAG, "%p, unable to open slave: %s, check configuration.", ptcp->pdriver, (char *)*paddr_table);
}
paddr_table++;
}
*port_obj = &(ptcp->base);
ESP_LOGD(TAG, "created object @%p", ptcp);
return MB_ENOERR;
error:
if (ptcp && ptcp->pdriver) {
(void)mbm_drv_unregister(ptcp->pdriver);
CRITICAL_SECTION_CLOSE(ptcp->base.lock);
}
free(ptcp);
return ret;
}
void mbm_port_tcp_delete(mb_port_base_t *inst)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
esp_err_t err = mbm_drv_unregister(port_obj->pdriver);
if (err != ESP_OK) {
ESP_LOGE(TAG, "driver unregister fail, returns (0x%d).", (uint16_t)err);
}
CRITICAL_SECTION_CLOSE(inst->lock);
free(port_obj);
}
void mbm_port_tcp_enable(mb_port_base_t *inst)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
//esp_err_t err = ESP_ERR_INVALID_STATE;
// if (!port_obj->pdriver->is_registered && !port_obj->pdriver) {
// err = mbm_drv_register(&port_obj->pdriver);
// MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG, "mb tcp port driver register failed.");
// }
(void)mbm_drv_start_task(port_obj->pdriver);
DRIVER_SEND_EVENT(port_obj->pdriver, MB_EVENT_RESOLVE, -1);
}
void mbm_port_tcp_disable(mb_port_base_t *inst)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
// Change the state of all slaves to close
DRIVER_SEND_EVENT(port_obj->pdriver, MB_EVENT_CLOSE, -1);
(void)mbm_drv_wait_status_flag(port_obj->pdriver, MB_FLAG_DISCONNECTED, MB_RECONNECT_TIME_MS);
//(void)mbm_drv_stop_task(port_obj->pdriver); // do not stop the task if we want to gracefully shutdown the task
}
bool mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
mb_slave_info_t *pinfo = port_obj->pdriver->mb_slave_curr_info;
MB_RETURN_ON_FALSE((pinfo), false, TAG, "incorrect current slave pointer.");
bool status = false;
size_t sz = mbm_drv_read(port_obj->pdriver, pinfo->fd, port_obj->ptemp_buf, MB_BUFFER_SIZE);
if (sz > MB_TCP_FUNC) {
uint16_t tid_counter = MB_TCP_MBAP_GET_FIELD(port_obj->ptemp_buf, MB_TCP_TID);
if (tid_counter == (pinfo->tid_counter - 1)) {
*ppframe = port_obj->ptemp_buf;
*plength = sz;
ESP_LOGW(TAG, "%p, "MB_SLAVE_FMT(", received packet TID = 0x%.4x:(0x%.4x), %p."),
port_obj->pdriver, pinfo->index, pinfo->sock_id, pinfo->addr_info.ip_addr_str,
tid_counter, pinfo->tid_counter, *ppframe);
uint64_t time = 0;
time = port_get_timestamp() - pinfo->send_time;
ESP_LOGW(TAG, "%p, "MB_SLAVE_FMT(", processing time[us] = %ju."), port_obj->pdriver, pinfo->index,
pinfo->sock_id, pinfo->addr_info.ip_addr_str, time);
status = true;
} else {
ESP_LOGE(TAG, "%p, "MB_SLAVE_FMT(", drop packet TID = 0x%.4x:0x%.4x, %p."),
port_obj->pdriver, pinfo->index, pinfo->sock_id,
pinfo->addr_info.ip_addr_str, tid_counter, pinfo->tid_counter, *ppframe);
}
}
return status;
}
bool mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
bool frame_sent = false;
// get slave descriptor from its address
mb_slave_info_t *pinfo = (mb_slave_info_t *)mbm_drv_get_slave_info_from_addr(port_obj->pdriver, address);
MB_RETURN_ON_FALSE((pinfo && (MB_GET_SLAVE_STATE(pinfo) >= MB_SOCK_STATE_CONNECTED)),
false, TAG, "the slave address #%d is not registered.", address);
if (pinfo && pframe) {
// Apply TID field to the frame before send
MB_TCP_MBAP_SET_FIELD(pframe, MB_TCP_TID, pinfo->tid_counter);
pframe[MB_TCP_UID] = (uint8_t)(pinfo->addr_info.uid);
}
ESP_LOGW(TAG, "%p, send fd: %d, sock_id: %d[%s], %p, len: %d",
port_obj->pdriver, pinfo->fd, pinfo->sock_id, pinfo->addr_info.node_name_str, pframe, length);
// Write data to the modbus vfs driver send queue of the slave
int write_length = mbm_drv_write(port_obj->pdriver, pinfo->fd, pframe, length);
if (write_length) {
frame_sent = true;
} else {
ESP_LOGE(TAG, "mbm_write fail, returns %d.", write_length);
}
// mb_port_tmr_respond_timeout_enable(inst); // the timer is set in the transport
return frame_sent;
}
void mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
mbm_drv_set_cb(port_obj->pdriver, conn_fp, arg);
}
// Timer handler to check timeout of socket response
bool mbm_port_timer_expired(void *inst)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
bool need_poll = false;
BaseType_t task_unblocked;
mb_event_info_t mb_event;
esp_err_t err = ESP_FAIL;
mb_port_tmr_disable(inst);
// If timer mode is respond timeout, the master event then turns EV_MASTER_EXECUTE status.
if (mb_port_get_cur_tmr_mode(inst) == MB_TMODE_RESPOND_TIMEOUT) {
// It is now to check solution.
mb_event.event_id = MB_EVENT_TIMEOUT;
mb_event.opt_fd = port_obj->pdriver->curr_slave_index;
err = esp_event_isr_post_to(port_obj->pdriver->event_loop_hdl, MB_EVENT_BASE(port_obj->pdriver),
(int32_t)MB_EVENT_TIMEOUT, (void *)&mb_event, sizeof(mb_event_info_t*), &task_unblocked);
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "Timeout event send error: %d", err);
}
need_poll = task_unblocked;
mb_port_evt_set_err_type(inst, EV_ERROR_RESPOND_TIMEOUT);
need_poll = mb_port_evt_post(inst, EVENT(EV_ERROR_PROCESS));
}
return need_poll;
}
mb_uid_info_t *mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t slave_addr, mb_sock_state_t exp_state)
{
mbm_tcp_port_t *port_obj = __containerof(inst, mbm_tcp_port_t, base);
mb_uid_info_t *paddr_info = NULL;
mb_slave_info_t *pinfo = mbm_drv_get_slave_info_from_addr(port_obj->pdriver, slave_addr);
if (pinfo && (MB_GET_SLAVE_STATE(pinfo) >= exp_state)) {
paddr_info = &pinfo->addr_info;
}
return paddr_info;
}
static void mbm_port_tcp_sync_event(void *inst, mb_sync_event_t sync_event)
{
switch(sync_event) {
case MB_SYNC_EVENT_RECV_OK:
mb_port_tmr_disable(inst);
mb_port_evt_set_err_type(inst, EV_ERROR_INIT);
mb_port_evt_post(inst, EVENT(EV_FRAME_RECEIVED));
break;
case MB_SYNC_EVENT_RECV_FAIL:
mb_port_tmr_disable(inst);
mb_port_evt_set_err_type(inst, EV_ERROR_RECEIVE_DATA);
mb_port_evt_post(inst, EVENT(EV_ERROR_PROCESS));
break;
case MB_SYNC_EVENT_SEND_OK:
mb_port_evt_post(inst, EVENT(EV_FRAME_SENT));
break;
default:
break;
}
}
#endif

View File

@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_event.h" // for esp event loop
#include "mb_common.h"
#include "mb_frame.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef enum _mb_sock_state mb_sock_state_t;
typedef struct _uid_info mb_uid_info_t;
void mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
mb_uid_info_t *mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t uid, mb_sock_state_t exp_state);
//bool mbm_port_timer_expired(void *inst);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <string.h>
#include "port_tcp_common.h"
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef struct
{
mb_port_base_t base;
// TCP communication properties
mb_tcp_opts_t tcp_opts;
uint64_t transaction_cnt;
uint16_t recv_length;
uint64_t send_time_stamp;
uint64_t recv_time_stamp;
uint32_t flags;
TaskHandle_t task_handle;
} mb_tcp_port_t;
/* ----------------------- Static variables & functions ----------------------*/
static const char *TAG = "mb_port.tcp.slave";
mb_err_enum_t mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **port_obj)
{
mb_tcp_port_t *ptcp = NULL;
ptcp = (mb_tcp_port_t*)calloc(1, sizeof(mb_tcp_port_t));
MB_RETURN_ON_FALSE((ptcp && port_obj), MB_EILLSTATE, TAG, "mb tcp port creation error.");
CRITICAL_SECTION_INIT(ptcp->base.lock);
return MB_ENOERR;
}
void mbs_port_tcp_delete(mb_port_base_t *inst)
{
mb_tcp_port_t *port_obj = __containerof(inst, mb_tcp_port_t, base);
//vTaskDelete(port_obj->task_handle);
CRITICAL_SECTION_CLOSE(inst->lock);
free(port_obj);
}
__attribute__((unused))
void mbs_port_tcp_enable(mb_port_base_t *inst)
{
// mb_tcp_port_t *port_obj = __containerof(inst, mb_tcp_port_t, base);
}
__attribute__((unused))
void mbs_port_tcp_disable(mb_port_base_t *inst)
{
// Todo: Temporary unused (needs update)
//mb_tcp_port_t *port_obj = __containerof(inst, mb_tcp_port_t, base);
}
__attribute__((unused))
bool mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
{
// mb_tcp_port_t *port_obj = __containerof(inst, mb_tcp_port_t, base);
return false;
}
bool mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length)
{
// mb_tcp_port_t *port_obj = __containerof(inst, mb_tcp_port_t, base);
return false;
}
#endif

View File

@@ -0,0 +1,718 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __has_include("esp_mac.h")
#include "esp_mac.h"
#endif
#include "port_tcp_master.h"
#include "port_tcp_utils.h"
#include "port_tcp_driver.h"
#define TAG "port.utils"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
// Check host name and/or fill the IP address structure
bool port_check_host_addr(const char *host_str, ip_addr_t *host_addr)
{
MB_RETURN_ON_FALSE((host_str), false, TAG, "wrong host name or IP.");
char cstr[HOST_STR_MAX_LEN];
char *pstr = &cstr[0];
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *paddr_list;
memset(&hint, 0, sizeof(hint));
// Do name resolution for both protocols
hint.ai_family = AF_UNSPEC;
hint.ai_flags = AI_ADDRCONFIG; // get IPV6 address if supported, otherwise IPV4
memset(&target_addr, 0, sizeof(target_addr));
// convert domain name to IP address
// Todo: check EAI_FAIL error when resolve host name
int ret = getaddrinfo(host_str, NULL, &hint, &paddr_list);
if (ret != 0) {
ESP_LOGD(TAG, "Incorrect host name or IP: %s", host_str);
return false;
}
if (paddr_list->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *)(paddr_list->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
pstr = ip4addr_ntoa_r(ip_2_ip4(&target_addr), cstr, sizeof(cstr));
}
#if CONFIG_LWIP_IPV6
else {
struct in6_addr addr6 = ((struct sockaddr_in6 *)(paddr_list->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
pstr = ip6addr_ntoa_r(ip_2_ip6(&target_addr), cstr, sizeof(cstr));
}
#endif
if (host_addr) {
*host_addr = target_addr;
}
ESP_LOGD(TAG, "Check name[IP]: \"%s\"[%s]", paddr_list->ai_canonname, pstr);
freeaddrinfo(paddr_list);
return true;
}
bool port_close_connection(mb_slave_info_t *pinfo)
{
if (!pinfo) {
return false;
}
if (pinfo->sock_id == -1) {
ESP_LOGE(TAG, "Wrong socket info or disconnected socket: %d, skip.", pinfo->sock_id);
return false;
}
if (shutdown(pinfo->sock_id, SHUT_RDWR) == -1) {
ESP_LOGV(TAG, "Shutdown failed sock %d, errno=%d", pinfo->sock_id, (int)errno);
}
close(pinfo->sock_id);
MB_SET_SLAVE_STATE(pinfo, MB_SOCK_STATE_OPENED);
pinfo->sock_id = -1;
return true;
}
mb_slave_info_t *port_get_current_info(void *ctx)
{
port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx);
if (!pdrv_ctx->mb_slave_curr_info) {
ESP_LOGE(TAG, "Incorrect current slave info.");
}
return pdrv_ctx->mb_slave_curr_info;
}
// The helper function to get time stamp in microseconds
int64_t port_get_timestamp(void)
{
int64_t time_stamp = esp_timer_get_time();
return time_stamp;
}
static void port_ms_to_tv(uint16_t timeout_ms, struct timeval *tv)
{
tv->tv_sec = timeout_ms / 1000;
tv->tv_usec = (timeout_ms - (tv->tv_sec * 1000)) * 1000;
}
void port_check_shutdown(void *ctx)
{
port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx);
// First check if the task is not flagged for shutdown
if (pdrv_ctx->close_done_sema) {
xSemaphoreGive(pdrv_ctx->close_done_sema);
vTaskDelete(NULL);
ESP_LOGW(TAG, "Destroy task...");
}
}
// Function returns time left for response processing according to response timeout
int64_t port_get_resp_time_left(mb_slave_info_t *pinfo)
{
if (!pinfo) {
return 0;
}
int64_t time_stamp = port_get_timestamp() - pinfo->send_time;
return (time_stamp > (1000 * MB_MASTER_TIMEOUT_MS_RESPOND)) ? 0 : (MB_MASTER_TIMEOUT_MS_RESPOND - (time_stamp / 1000) - 1);
}
int port_enqueue_packet(QueueHandle_t queue, uint8_t *pbuf, uint16_t len)
{
frame_entry_t frame_info = {0};
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (queue && pbuf) {
frame_info.tid = MB_TCP_MBAP_GET_FIELD(pbuf, MB_TCP_TID);
frame_info.uid = pbuf[MB_TCP_UID];
frame_info.pid = MB_TCP_MBAP_GET_FIELD(pbuf, MB_TCP_PID);
frame_info.len = MB_TCP_MBAP_GET_FIELD(pbuf, MB_TCP_LEN) + MB_TCP_UID;
if (len != frame_info.len) {
ESP_LOGE(TAG, "Packet TID (%x), length in frame %u != %u expected.", frame_info.tid, frame_info.len, len);
}
assert(xPortGetFreeHeapSize() > frame_info.len);
ret = queue_push(queue, pbuf, frame_info.len, &frame_info);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Packet TID (%x), data enqueue failed.", frame_info.tid);
// The packet send fail or the task which is waiting for event is already unblocked
return ERR_BUF;
} else {
ESP_LOGD(TAG, "Enqueue data, length=%d, TID=0x%.4x", frame_info.len, frame_info.tid);
return (int)frame_info.len;
}
} else {
ESP_LOGE(TAG, "Enqueue data fail, %p, length=%d.", pbuf, len);
}
return ERR_BUF;
}
int port_dequeue_packet(QueueHandle_t queue, frame_entry_t *pframe_info)
{
frame_entry_t frame_info = {0};
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (queue && pframe_info) {
ret = queue_pop(queue, NULL, MB_TCP_BUFF_MAX_SIZE, &frame_info);
if (ret == ESP_OK) {
if ((frame_info.pid == 0) && (frame_info.uid < MB_ADDRESS_MAX)) {
*pframe_info = frame_info;
ESP_LOGD(TAG, "Dequeue data, length=%d, TID=0x%.4x", (int)pframe_info->len, (int)pframe_info->tid);
return ERR_OK;
}
} else {
ESP_LOGE(TAG, "Dequeue data, failure %d", (int)ret);
}
}
return ERR_BUF;
}
static int port_get_buf(void *ctx, mb_slave_info_t *pinfo, uint8_t *pdst_buf, uint16_t len, uint16_t read_tick_ms)
{
int ret = 0;
uint8_t *pbuf = pdst_buf;
uint16_t bytes_left = len;
struct timeval time_val;
MB_RETURN_ON_FALSE((pinfo && (pinfo->sock_id > -1)), -1, TAG, "Try to read incorrect socket = #%d.", pinfo->sock_id);
// Set receive timeout for socket <= slave respond time
time_val.tv_sec = read_tick_ms / 1000;
time_val.tv_usec = (read_tick_ms % 1000) * 1000;
setsockopt(pinfo->sock_id, SOL_SOCKET, SO_RCVTIMEO, &time_val, sizeof(time_val));
// Receive data from connected client
while (bytes_left > 0) {
ret = recv(pinfo->sock_id, pbuf, bytes_left, 0);
if (ret < 0) {
if (errno == EINPROGRESS || errno == EAGAIN || errno == EWOULDBLOCK) {
// Read timeout occurred, check the timeout and return
//return 0;
} else if (errno == ENOTCONN) {
ESP_LOGE(TAG, "socket(#%d)(%s) connection closed, ret=%d, errno=%d.",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, ret, (int)errno);
// Socket connection closed
return ERR_CONN;
} else {
// Other error occurred during receiving
ESP_LOGE(TAG, "Socket(#%d)(%s) receive error, ret = %d, errno = %d(%s)",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, ret, (int)errno, strerror(errno));
return -1;
}
} else if (ret) {
pbuf += ret;
bytes_left -= ret;
}
port_check_shutdown(ctx);
}
return len;
}
int port_read_packet(void *ctx, mb_slave_info_t *pinfo)
{
uint16_t temp = 0;
int ret = 0;
uint8_t ptemp_buf[MB_TCP_BUFF_MAX_SIZE] = {0};
// Receive data from connected client
if (pinfo) {
MB_RETURN_ON_FALSE((pinfo->sock_id > 0), -1, TAG, "try to read incorrect socket = #%d.", pinfo->sock_id);
// Read packet header
ret = port_get_buf(ctx, pinfo, ptemp_buf, MB_TCP_UID, MB_READ_TICK);
if (ret < 0) {
pinfo->recv_err = ret;
return ret;
} else if (ret != MB_TCP_UID) {
ESP_LOGD(TAG, "Socket (#%d)(%s), fail to read modbus header. ret=%d",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, ret);
pinfo->recv_err = ERR_VAL;
return ERR_VAL;
}
temp = MB_TCP_MBAP_GET_FIELD(ptemp_buf, MB_TCP_PID);
if (temp != 0) {
pinfo->recv_err = ERR_BUF;
return ERR_BUF;
}
// If we have received the MBAP header we can analyze it and calculate
// the number of bytes left to complete the current response.
temp = MB_TCP_MBAP_GET_FIELD(ptemp_buf, MB_TCP_LEN);
if (temp > MB_TCP_BUFF_MAX_SIZE) {
ESP_LOGD("RCV", "Packet length: %d", temp);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, ptemp_buf, MB_TCP_FUNC, ESP_LOG_DEBUG);
pinfo->recv_err = ERR_BUF;
temp = MB_TCP_BUFF_MAX_SIZE; // read all remaining data from buffer
}
ret = port_get_buf(ctx, pinfo, &ptemp_buf[MB_TCP_UID], temp, MB_READ_TICK);
if (ret < 0) {
pinfo->recv_err = ret;
return ret;
} else if (ret != temp) {
pinfo->recv_err = ERR_VAL;
return ERR_VAL;
}
if (ptemp_buf[MB_TCP_UID] > MB_ADDRESS_MAX) {
pinfo->recv_err = ERR_BUF;
return ERR_BUF;
}
ret = port_enqueue_packet(pinfo->rx_queue, ptemp_buf, temp + MB_TCP_UID);
if (ret < 0) {
pinfo->recv_err = ret;
return ret;
}
pinfo->recv_counter++;
pinfo->recv_err = ERR_OK;
return ret + MB_TCP_FUNC;
}
return -1;
}
err_t port_set_blocking(mb_slave_info_t *pinfo, bool is_blocking)
{
if (!pinfo) {
return ERR_CONN;
}
// Set non blocking attribute for socket
uint32_t flags = fcntl(pinfo->sock_id, F_GETFL);
flags = is_blocking ? flags & ~O_NONBLOCK : flags | O_NONBLOCK;
if (fcntl(pinfo->sock_id, F_SETFL, flags) == -1) {
ESP_LOGE(TAG, "Socket(#%d)(%s), fcntl() call error=%d",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, (int)errno);
return ERR_WOULDBLOCK;
} else {
pinfo->is_blocking = ((flags & O_NONBLOCK) != O_NONBLOCK);
}
return ERR_OK;
}
void port_keep_alive(mb_slave_info_t *pinfo)
{
int optval = 1;
setsockopt(pinfo->sock_id, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
}
// Check connection for timeout helper
err_t port_check_alive(mb_slave_info_t *pinfo, uint32_t timeout_ms)
{
fd_set write_set;
fd_set err_set;
err_t err = -1;
struct timeval time_val;
if (pinfo && pinfo->sock_id != -1) {
FD_ZERO(&write_set);
FD_ZERO(&err_set);
FD_SET(pinfo->sock_id, &write_set);
FD_SET(pinfo->sock_id, &err_set);
port_ms_to_tv(timeout_ms, &time_val);
// Check if the socket is writable
err = select(pinfo->sock_id + 1, NULL, &write_set, &err_set, &time_val);
if ((err < 0) || FD_ISSET(pinfo->sock_id, &err_set)) {
if (errno == EINPROGRESS) {
err = ERR_INPROGRESS;
} else {
ESP_LOGV(TAG, MB_SLAVE_FMT(" connection, select write err(errno) = %d(%d)."),
pinfo->index, pinfo->sock_id, pinfo->addr_info.ip_addr_str, err, (int)errno);
err = ERR_CONN;
}
} else if (err == 0) {
ESP_LOGV(TAG, "Socket(#%d)(%s), connection timeout occurred, err(errno) = %d(%d).",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, err, (int)errno);
return ERR_INPROGRESS;
} else {
int opt_err = 0;
uint32_t opt_len = sizeof(opt_err);
// Check socket error
err = getsockopt(pinfo->sock_id, SOL_SOCKET, SO_ERROR, (void *)&opt_err, (socklen_t *)&opt_len);
if (opt_err != 0) {
ESP_LOGD(TAG, "Socket(#%d)(%s), sock error occurred (%d).",
pinfo->sock_id, pinfo->addr_info.ip_addr_str, opt_err);
return ERR_CONN;
}
ESP_LOGV(TAG, "Socket(#%d)(%s), is alive.",
pinfo->sock_id, pinfo->addr_info.ip_addr_str);
return ERR_OK;
}
} else {
err = ERR_CONN;
}
return err;
}
// Unblocking connect function
err_t port_connect(void *ctx, mb_slave_info_t *pinfo)
{
if (!pinfo) {
return ERR_CONN;
}
port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx);
err_t err = ERR_OK;
char str[HOST_STR_MAX_LEN];
char *pstr = NULL;
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *addr_list;
struct addrinfo *pcur_addr;
memset(&hint, 0, sizeof(hint));
// Do name resolution for both protocols
// hint.ai_family = AF_UNSPEC; Todo: Find a reason why AF_UNSPEC does not work
hint.ai_flags = AI_ADDRCONFIG; // get IPV6 address if supported, otherwise IPV4
hint.ai_family = (pinfo->addr_info.addr_type == MB_IPV4) ? AF_INET : AF_INET6;
hint.ai_socktype = (pinfo->addr_info.proto == MB_UDP) ? SOCK_DGRAM : SOCK_STREAM;
hint.ai_protocol = (pinfo->addr_info.proto == MB_UDP) ? IPPROTO_UDP : IPPROTO_TCP;
memset(&target_addr, 0, sizeof(target_addr));
if (asprintf(&pstr, "%u", pinfo->addr_info.port) == -1) {
abort();
}
// convert domain name to IP address
int ret = getaddrinfo(pinfo->addr_info.ip_addr_str, pstr, &hint, &addr_list);
free(pstr);
if (ret != 0) {
ESP_LOGE(TAG, "Cannot resolve host: %s", pinfo->addr_info.ip_addr_str);
return ERR_CONN;
}
for (pcur_addr = addr_list; pcur_addr != NULL; pcur_addr = pcur_addr->ai_next) {
if (pcur_addr->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *)(pcur_addr->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
pstr = ip4addr_ntoa_r(ip_2_ip4(&target_addr), str, sizeof(str));
}
#if CONFIG_LWIP_IPV6
else if (pcur_addr->ai_family == AF_INET6) {
struct in6_addr addr6 = ((struct sockaddr_in6 *)(pcur_addr->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
pstr = ip6addr_ntoa_r(ip_2_ip6(&target_addr), str, sizeof(str));
// Set scope id to fix routing issues with local address
((struct sockaddr_in6 *)(pcur_addr->ai_addr))->sin6_scope_id =
esp_netif_get_netif_impl_index(pdrv_ctx->network_iface_ptr);
}
#endif
if (pinfo->sock_id <= 0) {
pinfo->sock_id = socket(pcur_addr->ai_family, pcur_addr->ai_socktype, pcur_addr->ai_protocol);
if (pinfo->sock_id < 0) {
ESP_LOGE(TAG, "Unable to create socket: #%d, errno %d", pinfo->sock_id, (int)errno);
err = ERR_IF;
continue;
}
} else {
ESP_LOGV(TAG, "Socket (#%d)(%s) created.", pinfo->sock_id, str);
}
// Set non blocking attribute for socket
port_set_blocking(pinfo, false);
// Can return EINPROGRESS as an error which means
// that connection is in progress and should be checked later
err = connect(pinfo->sock_id, (struct sockaddr *)pcur_addr->ai_addr, pcur_addr->ai_addrlen);
if ((err < 0) && (errno == EINPROGRESS || errno == EALREADY)) {
// The unblocking connect is pending (check status later) or already connected
ESP_LOGV(TAG, "Socket(#%d)(%s) connection is pending, errno %d (%s).",
pinfo->sock_id, str, (int)errno, strerror(errno));
// Set keep alive flag in socket options
port_keep_alive(pinfo);
err = port_check_alive(pinfo, MB_TCP_CONNECTION_TIMEOUT_MS);
continue;
} else if ((err < 0) && (errno == EISCONN)) {
// Socket already connected
err = ERR_OK;
continue;
} else if (err != ERR_OK) {
// Other error occurred during connection
ESP_LOGV(TAG, "%p, "MB_SLAVE_FMT(" unable to connect, error=%d, errno %d (%s)"),
ctx, pinfo->index, pinfo->sock_id, str, err, (int)errno, strerror(errno));
port_close_connection(pinfo);
err = ERR_CONN;
} else {
ESP_LOGI(TAG, "%p, "MB_SLAVE_FMT(", successfully connected."),
ctx, pinfo->index, pinfo->sock_id, str);
continue;
}
}
freeaddrinfo(addr_list);
port_set_blocking(pinfo, true);
return err;
}
int port_write_poll(mb_slave_info_t *pinfo, const uint8_t *pframe, uint16_t frame_len, uint32_t timeout)
{
// Check if the socket is alive (writable and SO_ERROR == 0)
int res = (int)port_check_alive(pinfo, timeout);
if ((res < 0) && (res != ERR_INPROGRESS)) {
ESP_LOGE(TAG, MB_SLAVE_FMT(", is not writable, error: %d, errno %d"),
pinfo->index, pinfo->sock_id, pinfo->addr_info.ip_addr_str, res, (int)errno);
return res;
}
res = send(pinfo->sock_id, pframe, frame_len, TCP_NODELAY);
if (res < 0) {
ESP_LOGE(TAG, MB_SLAVE_FMT(", send data error: %d, errno %d"),
pinfo->index, pinfo->sock_id, pinfo->addr_info.ip_addr_str, res, (int)errno);
}
return res;
}
// Scan IP address according to IPV settings
int port_scan_addr_string(char *buffer, mb_uid_info_t *pslave_info)
{
char *phost_str = NULL;
unsigned int a[8] = {0};
int ret = 0;
uint16_t index = 0;
uint16_t port = 0;
MB_RETURN_ON_FALSE((buffer && (strlen(buffer) < (HOST_STR_MAX_LEN - 8)) && pslave_info),
-1, TAG, "check input parameters fail.");
#if CONFIG_LWIP_IPV6
// Configuration format:
// "12:2001:0db8:85a3:0000:0000:8a2e:0370:7334:502"
// "12:2001:0db8:85a3:0000:0000:8a2e:0370:7334"
ret = sscanf(buffer, "%" PRIu16 ";" IPV6STR ";%" PRIu16, &index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7], &port);
if ((ret == MB_STR_LEN_IDX_IP6) || (ret == MB_STR_LEN_IDX_IP6_PORT)) {
if (-1 == asprintf(&phost_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
abort();
}
pslave_info->node_name_str = phost_str;
pslave_info->ip_addr_str = phost_str;
pslave_info->uid = index;
pslave_info->fd = index;
pslave_info->port = (ret == MB_STR_LEN_IDX_IP6_PORT) ? port : 502;
pslave_info->addr_type = MB_IPV6;
pslave_info->proto = MB_TCP;
return ret;
}
// Configuration format:
// "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
ret = sscanf(buffer, IPV6STR, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
if (ret == MB_STR_LEN_IP6_ONLY) {
if (-1 == asprintf(&phost_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
abort();
}
pslave_info->node_name_str = phost_str;
pslave_info->ip_addr_str = phost_str;
pslave_info->uid = 0;
pslave_info->fd = 0;
pslave_info->port = 502;
pslave_info->addr_type = MB_IPV6;
pslave_info->proto = MB_TCP;
return ret;
}
#endif
// Configuration format:
// "192.168.1.1"
ret = sscanf(buffer, IPSTR, &a[0], &a[1], &a[2], &a[3]);
if (ret == MB_STR_LEN_IP4_ONLY) {
if (-1 == asprintf(&phost_str, IPSTR, a[0], a[1], a[2], a[3])) {
abort();
}
pslave_info->node_name_str = phost_str;
pslave_info->ip_addr_str = phost_str;
pslave_info->uid = 0;
pslave_info->fd = 0;
pslave_info->port = 502;
pslave_info->addr_type = MB_IPV4;
pslave_info->proto = MB_TCP;
return ret;
}
// Configuration format:
// "1:192.168.1.1:502"
ret = sscanf(buffer, "%" PRIu16 ";"IPSTR";%" PRIu16, &index, &a[0], &a[1], &a[2], &a[3], &port);
if ((ret == MB_STR_LEN_IDX_IP4_PORT) || (ret == MB_STR_LEN_IDX_IP4)) {
if (-1 == asprintf(&phost_str, IPSTR, a[0], a[1], a[2], a[3])) {
abort();
}
pslave_info->node_name_str = phost_str;
pslave_info->ip_addr_str = phost_str;
pslave_info->uid = index;
pslave_info->fd = index;
pslave_info->port = (ret == MB_STR_LEN_IDX_IP4_PORT) ? port : 502;
pslave_info->addr_type = MB_IPV4;
pslave_info->proto = MB_TCP;
return ret;
}
// Configuration format:
// "01:mb_slave_tcp_01:1502"
ret = sscanf(buffer, "%" PRIu16 ";%m[a-z0-9_];%" PRIu16, (uint16_t*)&index, &phost_str, &port);
if ((ret == MB_STR_LEN_HOST) || (ret == MB_STR_LEN_IDX_HOST_PORT)) {
pslave_info->node_name_str = (phost_str && strlen(phost_str)) ? phost_str : pslave_info->node_name_str;
pslave_info->ip_addr_str = (pslave_info->node_name_str) ? pslave_info->node_name_str : pslave_info->ip_addr_str;
pslave_info->uid = index;
pslave_info->port = (ret == MB_STR_LEN_IDX_HOST_PORT) ? port : 502;
pslave_info->addr_type = MB_IPV4;
pslave_info->proto = MB_TCP;
return ret;
}
// Configuration format:
// "mb_slave_tcp_01"
ret = sscanf(buffer, "%m[a-z0-9_]", &phost_str);
if (ret == MB_STR_LEN_HOST) {
pslave_info->node_name_str = (phost_str && strlen(phost_str)) ? phost_str : pslave_info->node_name_str;
pslave_info->ip_addr_str = (pslave_info->node_name_str) ? pslave_info->node_name_str : pslave_info->ip_addr_str;
pslave_info->uid = index;
pslave_info->port = 502;
pslave_info->addr_type = MB_IPV4;
pslave_info->proto = MB_TCP;
return ret;
}
return -1;
}
#ifdef MB_MDNS_IS_INCLUDED
// convert MAC from binary format to string
inline char *gen_mac_str(const uint8_t *mac, char *pref, char *mac_str)
{
sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
return mac_str;
}
inline char *gen_id_str(char *service_name, char *slave_id_str)
{
sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
return slave_id_str;
}
void port_start_mdns_service(void *ctx)
{
char temp_str[32] = {0};
uint8_t sta_mac[6] = {0};
esp_err_t err = ESP_ERR_INVALID_STATE;
err = esp_read_mac(sta_mac, ESP_MAC_WIFI_STA);
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG, "get STA mac fail, err = %d.", (uint16_t)err);
char *hostname = gen_mac_str(sta_mac, "mb_master_tcp_", temp_str);
// initialize mDNS
err = mdns_init();
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG, "mdns init fail, err = %d.", (uint16_t)err);
// set mDNS hostname (required if you want to advertise services)
err = mdns_hostname_set(hostname);
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG, "mdns set host name fail, err = %d.", (uint16_t)err);
ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
// set default mDNS instance name
err = mdns_instance_name_set("esp32_mb_master_tcp");
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG, "mdns instance name set fail, err = %d.", (uint16_t)err);
}
char *port_get_slave_ip_str(mdns_ip_addr_t *address, mb_addr_type_t addr_type)
{
mdns_ip_addr_t *a = address;
char *slave_ip_str = NULL;
while (a) {
if ((a->addr.type == ESP_IPADDR_TYPE_V6) && (addr_type == MB_IPV6)) {
if (-1 == asprintf(&slave_ip_str, IPV6STR, IPV62STR(a->addr.u_addr.ip6))) {
abort();
}
} else if ((a->addr.type == ESP_IPADDR_TYPE_V4) && (addr_type == MB_IPV4)) {
if (-1 == asprintf(&slave_ip_str, IPSTR, IP2STR(&(a->addr.u_addr.ip4)))) {
abort();
}
}
if (slave_ip_str) {
break;
}
a = a->next;
}
return slave_ip_str;
}
esp_err_t port_resolve_slave(uint8_t short_addr, mdns_result_t *result, char **resolved_ip,
mb_addr_type_t addr_type)
{
if (!short_addr || !result || !resolved_ip) {
return ESP_ERR_INVALID_ARG;
}
mdns_result_t *r = result;
int t;
char *slave_ip = NULL;
char slave_name[22] = {0};
if (sprintf(slave_name, "mb_slave_tcp_%02X", short_addr) < 0) {
ESP_LOGE(TAG, "Fail to create instance name for index: %d", short_addr);
abort();
}
for (; r; r = r->next) {
if ((r->ip_protocol == MDNS_IP_PROTOCOL_V4) && (addr_type == MB_IPV6)) {
continue;
} else if ((r->ip_protocol == MDNS_IP_PROTOCOL_V6) && (addr_type == MB_IPV4)) {
continue;
}
// Check host name for Modbus short address and
// append it into slave ip address table
if ((strcmp(r->instance_name, slave_name) == 0) && (r->port == CONFIG_FMB_TCP_PORT_DEFAULT)) {
printf(" PTR : %s\n", r->instance_name);
if (r->txt_count) {
printf(" TXT : [%u] ", r->txt_count);
for (t = 0; t < r->txt_count; t++) {
printf("%s=%s; ", r->txt[t].key, r->txt[t].value ? r->txt[t].value : "NULL");
}
printf("\n");
}
slave_ip = port_get_slave_ip_str(r->addr, addr_type);
if (slave_ip) {
ESP_LOGI(TAG, "Resolved slave %s[%s]:%u", r->hostname, slave_ip, r->port);
*resolved_ip = slave_ip;
return ESP_OK;
}
}
}
*resolved_ip = NULL;
ESP_LOGD(TAG, "Fail to resolve slave: %s", slave_name);
return ESP_ERR_NOT_FOUND;
}
int port_resolve_mdns_host(const char *host_name, char **paddr_str)
{
ESP_LOGW(TAG, "Query A: %s.local", host_name);
esp_ip4_addr_t addr;
addr.addr = 0;
char *pstr = NULL;
esp_err_t err = mdns_query_a(host_name, MB_MDNS_QUERY_TIME_MS, &addr);
if (err) {
if(err == ESP_ERR_NOT_FOUND){
ESP_LOGE(TAG, "Host: %s, was not found!", host_name);
return -1;
}
return -1;
}
if (asprintf(&pstr, IPSTR, IP2STR(&addr)) == -1) {
abort();
}
*paddr_str = pstr;
return strlen(pstr);
}
#endif // #ifdef MB_MDNS_IS_INCLUDED
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdatomic.h>
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_netif.h"
#include "port_tcp_common.h"
#if __has_include("esp_timer.h")
#include "esp_timer.h"
#endif
#if __has_include("esp_mac.h")
#include "esp_mac.h"
#endif
#if __has_include("mdns.h")
#include "mdns.h"
#endif
#define HOST_STR_MAX_LEN (64)
#if MB_MDNS_IS_INCLUDED
#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
#endif
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
#endif
#define MB_MDNS_PORT (502)
#define MB_READ_TICK (500)
#define MB_MDNS_QUERY_TIME_MS (2000)
#define MB_STR_LEN_HOST 1 // "mb_slave_tcp_01"
#define MB_STR_LEN_IDX_HOST 2 // "12:mb_slave_tcp_01"
#define MB_STR_LEN_IDX_HOST_PORT 3 // "01:mb_slave_tcp_01:1502"
#define MB_STR_LEN_IP4_ONLY 4 // "192.168.1.1"
#define MB_STR_LEN_IDX_IP4 5 // "1:192.168.1.1"
#define MB_STR_LEN_IDX_IP4_PORT 6 // "1:192.168.1.1:502"
#define MB_STR_LEN_IP6_ONLY 8 // "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
#define MB_STR_LEN_IDX_IP6 9 // "12:2001:0db8:85a3:0000:0000:8a2e:0370:7334"
#define MB_STR_LEN_IDX_IP6_PORT 10 // "12:2001:0db8:85a3:0000:0000:8a2e:0370:7334:502"
typedef struct _frame_queue_entry frame_entry_t;
typedef struct _mb_slave_info mb_slave_info_t;
typedef enum _addr_type_enum mb_tcp_addr_type_t;
bool port_check_host_addr(const char *host_str, ip_addr_t* host_addr);
mb_slave_info_t* port_get_current_info(void *ctx);
void port_check_shutdown(void *ctx);
int64_t port_get_resp_time_left(mb_slave_info_t* pinfo);
int port_enqueue_packet(QueueHandle_t queue, uint8_t *pbuf, uint16_t len);
int port_dequeue_packet(QueueHandle_t queue, frame_entry_t* pframe_info);
int port_read_packet(void *ctx, mb_slave_info_t* pinfo);
err_t port_set_blocking(mb_slave_info_t* pinfo, bool is_blocking);
void port_keep_alive(mb_slave_info_t* pinfo);
err_t port_check_alive(mb_slave_info_t* pinfo, uint32_t timeout_ms);
err_t port_connect(void *ctx, mb_slave_info_t* pinfo);
bool port_close_connection(mb_slave_info_t* pinfo);
int port_write_poll(mb_slave_info_t* pinfo, const uint8_t *pframe, uint16_t frame_len, uint32_t timeout);
int64_t port_get_timestamp(void);
typedef struct _uid_info mb_uid_info_t;
int port_scan_addr_string(char *buffer, mb_uid_info_t *pslave_info);
#if MB_MDNS_IS_INCLUDED
// convert MAC from binary format to string
char *gen_mac_str(const uint8_t *mac, char *pref, char *mac_str);
char *gen_id_str(char *service_name, char *slave_id_str);
void port_start_mdns_service();
typedef struct mdns_ip_addr_s mdns_ip_addr_t;
typedef struct mdns_result_s mdns_result_t;
char *port_get_slave_ip_str(mdns_ip_addr_t *address, mb_addr_type_t addr_type);
esp_err_t port_resolve_slave(uint8_t short_addr, mdns_result_t *result, char **resolved_ip, mb_addr_type_t addr_type);
int port_resolve_mdns_host(const char *host_name, char **paddr_str);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include "ascii_lrc.h"
/* ----------------------- functions ---------------------------------*/
uint8_t mb_char2bin(uint8_t char_val)
{
if ((char_val >= '0') && (char_val <= '9')) {
return (uint8_t)(char_val - '0');
} else if ((char_val >= 'A') && (char_val <= 'F')) {
return (uint8_t)(char_val - 'A' + 0x0A);
} else {
return 0xFF;
}
}
uint8_t mb_bin2char(uint8_t byte_val)
{
if (byte_val <= 0x09) {
return (uint8_t)('0' + byte_val);
} else if ((byte_val >= 0x0A) && (byte_val <= 0x0F)) {
return (uint8_t)(byte_val - 0x0A + 'A');
} else {
/* Programming error. */
assert(0);
}
return '0';
}
uint8_t __attribute__ ((unused)) mb_lrc(uint8_t *pframe, uint16_t length)
{
uint8_t lrc = 0; /* LRC char initialized */
while (length--) {
lrc += *pframe++; /* Add buffer byte without carry */
}
/* Return twos complement */
lrc = (uint8_t)(-((char)lrc));
return lrc;
}
// The helper function to fill ASCII frame buffer
int mb_ascii_set_buf(const uint8_t *pdata, uint8_t *pbuf, int bin_length)
{
int bin_idx = 0;
int frm_idx = 0;
uint8_t lrc = 0;
assert(pdata && pbuf);
pbuf[0] = MB_ASCII_START;
for (frm_idx = 1; (bin_idx < bin_length); bin_idx++) {
pbuf[frm_idx++] = mb_bin2char((uint8_t)(pdata[bin_idx] >> 4)); // High nibble
pbuf[frm_idx++] = mb_bin2char((uint8_t)(pdata[bin_idx] & 0X0F)); // Low nibble
lrc += pdata[bin_idx];
}
lrc = (uint8_t)(-((char)lrc));
pbuf[frm_idx++] = mb_bin2char((uint8_t)(lrc >> 4));
pbuf[frm_idx++] = mb_bin2char((uint8_t)(lrc & 0X0F));
pbuf[frm_idx++] = MB_ASCII_CR;
pbuf[frm_idx++] = MB_ASCII_LF;
return frm_idx;
}
int mb_ascii_get_binary_buf(uint8_t *pdata, int length)
{
int bin_idx = 0;
uint8_t lrc = 0;
assert(pdata);
if ((pdata[0] == ':') && (pdata[length - 1] == '\n') && (pdata[length - 2] == '\r')) {
for (int str_idx = 1; (str_idx < length) && (pdata[str_idx] > ' '); str_idx += 2) {
pdata[bin_idx] = (mb_char2bin(pdata[str_idx]) << 4); // High nibble
pdata[bin_idx] |= mb_char2bin(pdata[str_idx + 1]); // Low nibble
lrc += pdata[bin_idx++];
}
}
lrc = (uint8_t)(-((char)lrc));
bin_idx = ((lrc == 0) && (bin_idx == ((length - 3) >> 1))) ? bin_idx : -1;
return bin_idx;
}

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#define MB_ASCII_CR '\r' /*!< Default CR character for Modbus ASCII. */
#define MB_ASCII_LF '\n' /*!< Default LF character for Modbus ASCII. */
#define MB_ASCII_START ':' /*!< Start of frame for Modbus ASCII. */
/* ----------------------- Static functions ---------------------------------*/
uint8_t mb_char2bin(uint8_t char_val);
uint8_t mb_bin2char(uint8_t byte_val);
uint8_t mb_lrc(uint8_t *frame_ptr, uint16_t len_buf);
int mb_ascii_get_binary_buf(uint8_t *pdata, int length);
int mb_ascii_set_buf(const uint8_t *pdata, uint8_t *pbuf, int bin_length);

View File

@@ -0,0 +1,288 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ascii_transport.h"
#include "port_serial_common.h"
#include "port_common.h"
#include "mb_config.h"
#if (CONFIG_FMB_COMM_MODE_ASCII_EN)
static const char *TAG = "mb_transp.ascii_master";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
uint8_t snd_buf[MB_ASCII_SER_PDU_SIZE_MAX];
uint8_t rcv_buf[MB_ASCII_SER_PDU_SIZE_MAX];
uint8_t *pascii_puf;
uint16_t snd_pdu_len;
uint8_t *snd_buf_cur;
uint16_t snd_buf_cnt;
uint16_t rcv_buf_pos;
bool frame_is_broadcast;
volatile mb_tmr_mode_enum_t cur_tmr_mode;
} mbm_ascii_trasp_t;
static mb_err_enum_t mbm_ascii_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
static mb_err_enum_t mbm_ascii_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
static void mbm_ascii_transp_start(mb_trans_base_t *inst);
static void mbm_ascii_transp_stop(mb_trans_base_t *inst);
static void mbm_ascii_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static void mbm_ascii_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static bool mbm_ascii_transp_tmr_expired(void *inst);
static bool mbm_ascii_transp_rq_is_bcast(mb_trans_base_t *inst);
mb_err_enum_t mbm_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_inst), MB_EINVAL, TAG, "invalid options for the instance.");
mb_err_enum_t ret = MB_ENOERR;
mbm_ascii_trasp_t *transp = NULL;
transp = (mbm_ascii_trasp_t *)calloc(1, sizeof(mbm_ascii_trasp_t));
transp->pascii_puf = calloc(1, MB_ASCII_SER_PDU_SIZE_MAX);
MB_RETURN_ON_FALSE((transp && transp->pascii_puf), MB_EILLSTATE, TAG, "no mem for ascii master transport instance.");
CRITICAL_SECTION_INIT(transp->base.lock);
CRITICAL_SECTION_LOCK(transp->base.lock);
transp->base.frm_rcv = mbm_ascii_transp_receive;
transp->base.frm_send = mbm_ascii_transp_send;
transp->base.frm_start = mbm_ascii_transp_start;
transp->base.frm_stop = mbm_ascii_transp_stop;
transp->base.get_rx_frm = mbm_ascii_transp_get_rcv_buf;
transp->base.get_tx_frm = mbm_ascii_transp_get_snd_buf;
transp->base.frm_delete = mbm_ascii_transp_delete;
transp->base.frm_is_bcast = mbm_ascii_transp_rq_is_bcast;
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mb_port_ser_create(ser_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "serial port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, (MB_ASCII_TIMEOUT_MS * MB_TIMER_TICS_PER_MS));
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "timer port creation, err: %d", ret);
// Override default response time if defined
if (ser_opts->response_tout_ms) {
mb_port_tmr_set_response_time(port_obj, ser_opts->response_tout_ms);
}
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "event port creation, err: %d", ret);
transp->base.port_obj = port_obj;
// Set callback function pointer for the timer
port_obj->cb.tmr_expired = mbm_ascii_transp_tmr_expired;
port_obj->cb.tx_empty = NULL;
port_obj->cb.byte_rcvd = NULL;
port_obj->arg = (void *)transp;
transp->port_obj = port_obj;
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
return MB_ENOERR;
error:
free((void *)transp->pascii_puf);
transp->pascii_puf = NULL;
if (port_obj) {
free(port_obj->event_obj);
free(port_obj->timer_obj);
}
free(port_obj);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbm_ascii_transp_delete(mb_trans_base_t *inst)
{
mbm_ascii_trasp_t *trans = __containerof(inst, mbm_ascii_trasp_t, base);
mb_port_tmr_delete(trans->base.port_obj);
mb_port_evt_delete(trans->base.port_obj);
mb_port_ser_delete(trans->base.port_obj);
free((void *)trans->pascii_puf);
CRITICAL_SECTION_CLOSE(inst->lock);
free(trans);
return true;
}
static void mbm_ascii_transp_start(mb_trans_base_t *inst)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
CRITICAL_SECTION(inst->lock) {
mb_port_ser_enable(inst->port_obj);
mb_port_tmr_enable(inst->port_obj);
};
/* No special startup required for ASCII. */
(void)mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
}
static void mbm_ascii_transp_stop(mb_trans_base_t *inst)
{
CRITICAL_SECTION(inst->lock) {
mb_port_ser_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbm_ascii_transp_receive(mb_trans_base_t *inst, uint8_t *prcv_addr, uint8_t **ppframe_buf, uint16_t *pbuf_len)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
mb_err_enum_t status = MB_ENOERR;
if (!pbuf_len) {
return MB_EIO;
}
uint8_t *pbuf = (uint8_t *)transp->rcv_buf;
uint16_t length = *pbuf_len;
if (mb_port_ser_recv_data(inst->port_obj, &pbuf, &length) == false)
{
return MB_EPORTERR;
}
assert(length < MB_ASCII_SER_PDU_SIZE_MAX);
// Convert the received ascii frame buffer to the binary representation
int ret = mb_ascii_get_binary_buf(pbuf, length);
/* Check length and LRC checksum */
if (ret >= MB_ASCII_SER_PDU_SIZE_MIN) {
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*prcv_addr = pbuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and LRC checksum.
*/
*pbuf_len = (uint16_t)(ret - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC);
transp->rcv_buf_pos = ret;
/* Return the start of the Modbus PDU to the caller. */
*ppframe_buf = (uint8_t *)&pbuf[MB_SER_PDU_PDU_OFF];
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbm_ascii_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t frame_len)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
mb_err_enum_t status = MB_ENOERR;
if (slv_addr > MB_MASTER_TOTAL_SLAVE_NUM) {
return MB_EINVAL;
}
if (frame_ptr && frame_len) {
/* First byte before the Modbus-PDU is the slave address. */
transp->snd_buf_cur = (uint8_t *)frame_ptr - 1;
transp->snd_buf_cnt = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
transp->snd_buf_cur[MB_SER_PDU_ADDR_OFF] = slv_addr;
transp->snd_buf_cnt += frame_len;
/* Prepare the ASCII buffer and send it to port */
int ascii_len = mb_ascii_set_buf(transp->snd_buf_cur, (uint8_t *)transp->pascii_puf, transp->snd_buf_cnt);
if (ascii_len > MB_ASCII_SER_PDU_SIZE_MIN) {
bool ret = mb_port_ser_send_data(inst->port_obj, (uint8_t *)transp->pascii_puf, ascii_len);
if (!ret) {
return MB_EPORTERR;
}
transp->frame_is_broadcast = (slv_addr == MB_ADDRESS_BROADCAST) ? true : false;
// If the frame is broadcast, master will enable timer of convert delay,
// else master will enable timer of respond timeout. */
if (transp->frame_is_broadcast) {
mb_port_tmr_convert_delay_enable(transp->base.port_obj);
} else {
mb_port_tmr_respond_timeout_enable(transp->base.port_obj);
}
} else {
status = MB_EIO;
}
} else {
status = MB_EIO;
}
return status;
}
// The receive fsm function (not implemented for this transport)
__attribute__((unused))
static bool mbm_ascii_transp_rcv_fsm(mb_trans_base_t *inst)
{
return false;
}
// The send fsm function (not implemented for this transport)
__attribute__((unused))
static bool mbm_ascii_transp_snd_fsm(mb_trans_base_t *inst)
{
return false;
}
// The timer expired function
static bool mbm_ascii_transp_tmr_expired(void *inst)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
bool need_poll = false;
mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(transp->base.port_obj);
mb_port_tmr_disable(transp->base.port_obj);
switch(timer_mode) {
case MB_TMODE_T35:
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
ESP_EARLY_LOGD(TAG, "%p:EV_READY", transp->base.descr.parent);
break;
case MB_TMODE_RESPOND_TIMEOUT:
mb_port_evt_set_err_type(transp->base.port_obj, EV_ERROR_RESPOND_TIMEOUT);
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_ERROR_PROCESS));
ESP_EARLY_LOGD(TAG, "%p:EV_ERROR_RESPOND_TIMEOUT", transp->base.descr.parent);
break;
case MB_TMODE_CONVERT_DELAY:
/* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_EXECUTE));
ESP_EARLY_LOGD(TAG, "%p:MB_TMODE_CONVERT_DELAY", transp->base.descr.parent);
break;
default:
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
break;
}
return need_poll;
}
static void mbm_ascii_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->rcv_buf[MB_PDU_FUNC_OFF];
}
}
static void mbm_ascii_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->snd_buf[MB_ASCII_SER_PDU_PDU_OFF];
}
}
// void mb_ascii_set_cur_tmr_mode(mb_ascii_tr_struct* inst, mb_tmr_mode_enum_t tmr_mode);
static bool mbm_ascii_transp_rq_is_bcast(mb_trans_base_t *inst)
{
mbm_ascii_trasp_t *transp = __containerof(inst, mbm_ascii_trasp_t, base);
return transp->frame_is_broadcast;
}
#endif

View File

@@ -0,0 +1,238 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ascii_transport.h"
#include "port_serial_common.h"
#include "sdkconfig.h"
#if (CONFIG_FMB_COMM_MODE_ASCII_EN)
static const char *TAG = "mb_transp.ascii_slave";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
// private properties
volatile uint8_t pdu_buf[MB_ASCII_SER_PDU_SIZE_MAX];
uint8_t *rcv_buf;
uint8_t *pascii_puf;
uint16_t snd_pdu_len;
uint8_t *snd_buf_cur;
uint16_t snd_buf_cnt;
uint16_t rcv_buf_pos;
volatile mb_tmr_mode_enum_t cur_tmr_mode;
} mbs_ascii_trasp_t;
mb_err_enum_t mbs_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
static void mbs_ascii_transp_start(mb_trans_base_t *inst);
static void mbs_ascii_transp_stop(mb_trans_base_t *inst);
static mb_err_enum_t mbs_ascii_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
static mb_err_enum_t mbs_ascii_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
static bool mbs_ascii_transp_rcv_fsm(mb_trans_base_t *inst);
static bool mbs_ascii_transp_snd_fsm(mb_trans_base_t *inst);
static bool mbs_ascii_transp_tmr_expired(void *inst);
void mbs_ascii_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static void mbs_ascii_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
mb_err_enum_t mbs_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_inst), MB_EINVAL, TAG, "invalid options for the instance.");
mb_err_enum_t ret = MB_ENOERR;
mbs_ascii_trasp_t *transp = NULL;
transp = (mbs_ascii_trasp_t *)calloc(1, sizeof(mbs_ascii_trasp_t));
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for the %s instance.", TAG);
transp->pascii_puf = calloc(1, MB_ASCII_SER_PDU_SIZE_MAX);
MB_RETURN_ON_FALSE((transp && transp->pascii_puf), MB_EILLSTATE, TAG, "no mem for the %s instance.", TAG);
CRITICAL_SECTION_INIT(transp->base.lock);
transp->base.frm_rcv = mbs_ascii_transp_receive;
transp->base.frm_send = mbs_ascii_transp_send;
transp->base.frm_start = mbs_ascii_transp_start;
transp->base.frm_stop = mbs_ascii_transp_stop;
transp->base.get_rx_frm = mbs_ascii_transp_get_rcv_buf;
transp->base.get_tx_frm = mbs_ascii_transp_get_snd_buf;
transp->base.frm_delete = mbs_ascii_transp_delete;
transp->base.frm_is_bcast = NULL;
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mb_port_ser_create(ser_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "serial port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, (MB_ASCII_TIMEOUT_MS * MB_TIMER_TICS_PER_MS));
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "timer port creation, err: %d", ret);
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "event port creation, err: %d", ret);
transp->base.port_obj = port_obj;
transp->rcv_buf = (uint8_t *)&transp->pdu_buf[0];
// Set callback function pointer for the timer
port_obj->cb.tmr_expired = mbs_ascii_transp_tmr_expired;
port_obj->cb.tx_empty = NULL;
port_obj->cb.byte_rcvd = NULL;
port_obj->arg = (void *)transp;
transp->port_obj = port_obj;
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
return MB_ENOERR;
error:
free(transp->pascii_puf);
transp->pascii_puf = NULL;
if (port_obj) {
free(port_obj->event_obj);
free(port_obj->timer_obj);
}
free(port_obj);
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbs_ascii_transp_delete(mb_trans_base_t *inst)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
mb_port_tmr_delete(transp->base.port_obj);
mb_port_evt_delete(transp->base.port_obj);
mb_port_ser_delete(transp->base.port_obj);
free(transp->pascii_puf);
CRITICAL_SECTION_CLOSE(inst->lock);
free(transp);
return true;
}
static void mbs_ascii_transp_start(mb_trans_base_t *inst)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
CRITICAL_SECTION(inst->lock) {
mb_port_ser_enable(inst->port_obj);
mb_port_tmr_enable(inst->port_obj);
};
/* No special startup required for ASCII. */
(void)mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
}
static void mbs_ascii_transp_stop(mb_trans_base_t *inst)
{
CRITICAL_SECTION(inst->lock) {
mb_port_ser_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbs_ascii_transp_receive(mb_trans_base_t *inst, uint8_t *prcv_addr, uint8_t **ppframe_buf, uint16_t *pbuf_len)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
mb_err_enum_t status = MB_ENOERR;
assert(transp->rcv_buf);
uint8_t *pbuf = (uint8_t *)transp->rcv_buf;
uint16_t length = *pbuf_len;
if (mb_port_ser_recv_data(inst->port_obj, &pbuf, &length) == false) {
return MB_EPORTERR;
}
assert(length < MB_ASCII_SER_PDU_SIZE_MAX);
// Convert the received ascii frame buffer to the binary representation
int ret = mb_ascii_get_binary_buf(pbuf, length);
/* Check length and CRC checksum */
if (ret >= MB_ASCII_SER_PDU_SIZE_MIN) {
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*prcv_addr = pbuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and LRC checksum.
*/
*pbuf_len = (uint16_t)(ret - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC);
transp->rcv_buf_pos = ret;
/* Return the start of the Modbus PDU to the caller. */
*ppframe_buf = (uint8_t *)&pbuf[MB_SER_PDU_PDU_OFF];
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbs_ascii_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t frame_len)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
mb_err_enum_t status = MB_ENOERR;
if (slv_addr > MB_MASTER_TOTAL_SLAVE_NUM) {
return MB_EINVAL;
}
if (frame_ptr && frame_len) {
/* First byte before the Modbus-PDU is the slave address. */
transp->snd_buf_cur = (uint8_t *)frame_ptr - 1;
transp->snd_buf_cnt = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
transp->snd_buf_cur[MB_SER_PDU_ADDR_OFF] = slv_addr;
transp->snd_buf_cnt += frame_len;
/* Prepare the ASCII buffer and send it to port */
int ascii_len = mb_ascii_set_buf(transp->snd_buf_cur, (uint8_t *)transp->pascii_puf, transp->snd_buf_cnt);
if (ascii_len > MB_ASCII_SER_PDU_SIZE_MIN) {
bool ret = mb_port_ser_send_data(inst->port_obj, (uint8_t *)transp->pascii_puf, ascii_len);
if (!ret) {
return MB_EPORTERR;
}
} else {
status = MB_EIO;
}
} else {
status = MB_EIO;
}
return status;
}
__attribute__((unused))
static bool mbs_ascii_transp_rcv_fsm(mb_trans_base_t *inst)
{
return false;
}
__attribute__((unused))
static bool mbs_ascii_transp_snd_fsm(mb_trans_base_t *inst)
{
return false;
}
static bool mbs_ascii_transp_tmr_expired(void *inst)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
mb_port_tmr_disable(transp->base.port_obj);
return false;
}
void mbs_ascii_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
assert(transp->rcv_buf);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->rcv_buf[MB_PDU_FUNC_OFF];
}
}
static void mbs_ascii_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_ascii_trasp_t *transp = __containerof(inst, mbs_ascii_trasp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->pdu_buf[MB_PDU_FUNC_OFF];
}
}
#endif

View File

@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "mb_config.h"
#include "mb_common.h"
#include "mb_types.h"
#include "mb_frame.h"
#include "mb_proto.h"
#include "transport_common.h"
#include "port_common.h"
#include "ascii_lrc.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_ASCII_EN)
/* ----------------------- Defines ------------------------------------------*/
#define MB_ASCII_SER_PDU_SIZE_MIN 5 /*!< Minimum size of a Modbus ASCII frame. */
#define MB_ASCII_SER_PDU_SIZE_MAX MB_SER_PDU_SIZE_MAX * 2 /*!< Maximum size of a Modbus ASCII frame. */
#define MB_ASCII_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */
#define MB_ASCII_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_ASCII_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct mb_trans_base_t mb_trans_base_t;
mb_err_enum_t mbm_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
mb_err_enum_t mbs_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
bool mbs_ascii_transp_delete(mb_trans_base_t *inst);
bool mbm_ascii_transp_delete(mb_trans_base_t *inst);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2010 Christian Walter
*
* SPDX-License-Identifier: BSD-3-Clause
*
* SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD
*/
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbcrc.c, v 1.7 2007/02/18 23:50:27 wolti Exp $
*/
/* ----------------------- Platform includes --------------------------------*/
#include "mb_common.h"
static const uint8_t crc_hi_tab[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
static const uint8_t crc_lo_tab[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};
uint16_t
mb_crc16(uint8_t *frame_ptr, uint16_t len_buf)
{
uint8_t crc_hi = 0xFF;
uint8_t crc_lo = 0xFF;
int idx;
while (len_buf--) {
idx = crc_lo ^ *(frame_ptr++);
crc_lo = (uint8_t)(crc_hi ^ crc_hi_tab[idx]);
crc_hi = crc_lo_tab[idx];
}
return (uint16_t)(crc_hi << 8 | crc_lo);
}

View File

@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2010 Christian Walter
*
* SPDX-License-Identifier: BSD-3-Clause
*
* SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD
*/
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* File: $Id: mbcrc.h, v 1.5 2006/12/07 22:10:34 wolti Exp $
*/
#pragma once
uint16_t mb_crc16(uint8_t *frame_ptr, uint16_t len_buf);

View File

@@ -0,0 +1,299 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rtu_transport.h"
#include "port_serial_common.h"
#include "port_common.h"
#include "mb_config.h"
#if (CONFIG_FMB_COMM_MODE_RTU_EN)
static const char *TAG = "mb_transp.rtu_master";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
uint8_t snd_buf[MB_RTU_SER_PDU_SIZE_MAX];
uint8_t rcv_buf[MB_RTU_SER_PDU_SIZE_MAX];
uint16_t snd_pdu_len;
uint8_t *snd_buf_cur;
uint16_t snd_buf_cnt;
uint16_t rcv_buf_pos;
bool frame_is_broadcast;
volatile mb_tmr_mode_enum_t cur_tmr_mode;
mb_rtu_state_enum_t state;
} mbm_rtu_transp_t;
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
static void mbm_rtu_transp_start(mb_trans_base_t *inst);
static void mbm_rtu_transp_stop(mb_trans_base_t *inst);
static mb_err_enum_t mbm_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
static mb_err_enum_t mbm_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
static bool mbm_rtu_transp_rcv_fsm(mb_trans_base_t *inst);
static bool mbm_rtu_transp_snd_fsm(mb_trans_base_t *inst);
static bool mbm_rtu_transp_tmr_35_expired(void *inst);
static void mbm_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static void mbm_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
// static uint16_t mbm_rtu_transp_get_snd_len(mb_trans_base_t *inst);
static void mbm_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len);
static bool mbm_rtu_transp_rq_is_bcast(mb_trans_base_t *inst);
bool mbm_rtu_transp_delete(mb_trans_base_t *inst);
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_inst), MB_EINVAL, TAG, "invalid options for the instance.");
mb_err_enum_t ret = MB_ENOERR;
mbm_rtu_transp_t *transp = NULL;
transp = (mbm_rtu_transp_t *)calloc(1, sizeof(mbm_rtu_transp_t));
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for %s instance.", TAG);
CRITICAL_SECTION_INIT(transp->base.lock);
CRITICAL_SECTION_LOCK(transp->base.lock);
transp->base.frm_rcv = mbm_rtu_transp_receive;
transp->base.frm_send = mbm_rtu_transp_send;
transp->base.frm_start = mbm_rtu_transp_start;
transp->base.frm_stop = mbm_rtu_transp_stop;
transp->base.get_rx_frm = mbm_rtu_transp_get_rcv_buf;
transp->base.get_tx_frm = mbm_rtu_transp_get_snd_buf;
transp->base.frm_delete = mbm_rtu_transp_delete;
transp->base.frm_is_bcast = mbm_rtu_transp_rq_is_bcast;
// Copy parent object descriptor
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mb_port_ser_create(ser_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "serial port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, MB_RTU_GET_T35_VAL(ser_opts->baudrate));
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "timer port creation, err: %d", ret);
// Override default response time if defined
if (ser_opts->response_tout_ms) {
mb_port_tmr_set_response_time(port_obj, ser_opts->response_tout_ms);
}
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "event port creation, err: %d", ret);
transp->base.port_obj = port_obj;
// Set callback function pointer for the timer
port_obj->cb.tmr_expired = mbm_rtu_transp_tmr_35_expired;
port_obj->cb.tx_empty = NULL;
port_obj->cb.byte_rcvd = NULL;
port_obj->arg = (void *)transp;
transp->port_obj = port_obj; // register the created port object
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
return MB_ENOERR;
error:
if (port_obj->timer_obj) {
mb_port_tmr_delete(port_obj);
}
if (port_obj->event_obj) {
mb_port_evt_delete(port_obj);
}
if (port_obj) {
mb_port_ser_delete(port_obj);
}
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbm_rtu_transp_delete(mb_trans_base_t *inst)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
mb_port_ser_delete(transp->base.port_obj);
mb_port_tmr_delete(transp->base.port_obj);
mb_port_evt_delete(transp->base.port_obj);
CRITICAL_SECTION_CLOSE(inst->lock);
free(transp);
return true;
}
static void mbm_rtu_transp_start(mb_trans_base_t *inst)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
transp->state = MB_RTU_STATE_INIT;
CRITICAL_SECTION(inst->lock) {
mb_port_ser_enable(inst->port_obj);
mb_port_tmr_enable(inst->port_obj);
};
/* No special startup required for RTU. */
(void)mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
}
static void mbm_rtu_transp_stop(mb_trans_base_t *inst)
{
CRITICAL_SECTION(inst->lock) {
mb_port_ser_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbm_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *prcv_addr, uint8_t **ppframe_buf, uint16_t *pbuf_len)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
if (!pbuf_len || !prcv_addr || !ppframe_buf || !pbuf_len) {
return MB_EIO;
}
mb_err_enum_t status = MB_ENOERR;
uint8_t *pbuf = (uint8_t *)transp->rcv_buf;
uint16_t length = *pbuf_len;
if (mb_port_ser_recv_data(inst->port_obj, &pbuf, &length) == false) {
*pbuf_len = 0;
return MB_EPORTERR;
}
assert(length < MB_RTU_SER_PDU_SIZE_MAX);
assert(pbuf);
/* Check length and CRC checksum */
if ((length >= MB_RTU_SER_PDU_SIZE_MIN)
&& (mb_crc16((uint8_t *)pbuf, length) == 0)) {
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*prcv_addr = pbuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and CRC checksum.
*/
*pbuf_len = (uint16_t)(length - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);
transp->rcv_buf_pos = length;
/* Return the start of the Modbus PDU to the caller. */
*ppframe_buf = (uint8_t *)&pbuf[MB_SER_PDU_PDU_OFF];
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbm_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t frame_len)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
mb_err_enum_t status = MB_ENOERR;
uint16_t crc16 = 0;
if (slv_addr > MB_MASTER_TOTAL_SLAVE_NUM) {
return MB_EINVAL;
}
if (frame_ptr && frame_len) {
/* First byte before the Modbus-PDU is the slave address. */
transp->snd_buf_cur = (uint8_t *)frame_ptr - 1;
transp->snd_buf_cnt = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
transp->snd_buf_cur[MB_SER_PDU_ADDR_OFF] = slv_addr;
transp->snd_buf_cnt += frame_len;
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
crc16 = mb_crc16((uint8_t *) transp->snd_buf_cur, transp->snd_buf_cnt);
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 & 0xFF);
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 >> 8);
bool ret = mb_port_ser_send_data(inst->port_obj, (uint8_t *)transp->snd_buf_cur, transp->snd_buf_cnt);
if (!ret) {
return MB_EPORTERR;
}
transp->frame_is_broadcast = (slv_addr == MB_ADDRESS_BROADCAST) ? true : false;
// If the frame is broadcast, master will enable timer of convert delay,
// else master will enable timer of respond timeout. */
if (transp->frame_is_broadcast) {
mb_port_tmr_convert_delay_enable(transp->base.port_obj);
} else {
mb_port_tmr_respond_timeout_enable(transp->base.port_obj);
}
} else {
status = MB_EIO;
}
return status;
}
__attribute__((unused))
static bool mbm_rtu_transp_rcv_fsm(mb_trans_base_t *inst)
{
return false;
}
__attribute__((unused))
static bool mbm_rtu_transp_snd_fsm(mb_trans_base_t *inst)
{
return false;
}
static bool mbm_rtu_transp_tmr_35_expired(void *inst)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
bool need_poll = false;
mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(transp->base.port_obj);
mb_port_tmr_disable(transp->base.port_obj);
switch(timer_mode) {
case MB_TMODE_T35:
//need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
//ESP_EARLY_LOGD(TAG, "%p:EV_READY", transp->base.descr.parent);
break;
case MB_TMODE_RESPOND_TIMEOUT:
mb_port_evt_set_err_type(transp->base.port_obj, EV_ERROR_RESPOND_TIMEOUT);
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_ERROR_PROCESS));
ESP_EARLY_LOGW(TAG, "%p:EV_ERROR_RESPOND_TIMEOUT", transp->base.descr.parent);
break;
case MB_TMODE_CONVERT_DELAY:
/* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_EXECUTE));
ESP_EARLY_LOGD(TAG, "%p:MB_TMODE_CONVERT_DELAY", transp->base.descr.parent);
break;
default:
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
break;
}
return need_poll;
}
static void mbm_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->rcv_buf[MB_PDU_FUNC_OFF];
}
}
static void mbm_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->snd_buf[MB_RTU_SER_PDU_PDU_OFF];
}
}
__attribute__((unused))
static void mbm_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
transp->snd_buf_cnt = snd_pdu_len;
}
}
static bool mbm_rtu_transp_rq_is_bcast(mb_trans_base_t *inst)
{
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
return transp->frame_is_broadcast;
}
#endif

View File

@@ -0,0 +1,255 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rtu_transport.h"
#include "port_serial_common.h"
#if (CONFIG_FMB_COMM_MODE_RTU_EN)
static const char *TAG = "mb_transp.rtu_slave";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
uint8_t snd_buf[MB_RTU_SER_PDU_SIZE_MAX]; // pdu_buf
uint8_t rcv_buf[MB_RTU_SER_PDU_SIZE_MAX];
uint16_t snd_pdu_len;
uint8_t *snd_buf_cur;
uint16_t snd_buf_cnt;
uint16_t rcv_buf_pos;
volatile mb_tmr_mode_enum_t cur_tmr_mode;
mb_rtu_state_enum_t state;
} mbs_rtu_transp_t;
mb_err_enum_t mbs_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
static void mbs_rtu_transp_start(mb_trans_base_t *inst);
static void mbs_rtu_transp_stop(mb_trans_base_t *inst);
static mb_err_enum_t mbs_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
static mb_err_enum_t mbs_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
static bool mbs_rtu_transp_rcv_fsm(mb_trans_base_t *inst);
static bool mbs_rtu_transp_snd_fsm(mb_trans_base_t *inst);
static bool mbs_rtu_transp_tmr_35_expired(void *inst);
static void mbs_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
void mbs_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static uint16_t mbs_rtu_transp_get_snd_len(mb_trans_base_t *inst);
static void mbs_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len);
bool mbs_rtu_transp_delete(mb_trans_base_t *inst);
mb_err_enum_t mbs_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst)
{
MB_RETURN_ON_FALSE((ser_opts && in_out_inst), MB_EINVAL, TAG, "invalid options for the instance.");
mb_err_enum_t ret = MB_ENOERR;
mbs_rtu_transp_t *transp = NULL;
transp = (mbs_rtu_transp_t *)calloc(1, sizeof(mbs_rtu_transp_t));
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for rtu slave transport instance.");
CRITICAL_SECTION_INIT(transp->base.lock);
CRITICAL_SECTION_LOCK(transp->base.lock);
transp->base.frm_rcv = mbs_rtu_transp_receive;
transp->base.frm_send = mbs_rtu_transp_send;
transp->base.frm_start = mbs_rtu_transp_start;
transp->base.frm_stop = mbs_rtu_transp_stop;
transp->base.get_rx_frm = mbs_rtu_transp_get_rcv_buf;
transp->base.get_tx_frm = mbs_rtu_transp_get_snd_buf;
transp->base.frm_delete = mbs_rtu_transp_delete;
transp->base.frm_is_bcast = NULL;
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mb_port_ser_create(ser_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "serial port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, MB_RTU_GET_T35_VAL(ser_opts->baudrate));
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "timer port creation, err: %d", ret);
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "event port creation, err: %d", ret);
transp->base.port_obj = port_obj;
// Set callback function pointer for the timer
port_obj->cb.tmr_expired = mbs_rtu_transp_tmr_35_expired;
port_obj->cb.tx_empty = NULL;
port_obj->cb.byte_rcvd = NULL;
port_obj->arg = (void *)transp;
transp->port_obj = port_obj;
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
return MB_ENOERR;
error:
if (port_obj) {
free(port_obj->event_obj);
free(port_obj->timer_obj);
}
free(port_obj);
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbs_rtu_transp_delete(mb_trans_base_t *inst)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
mb_port_ser_delete(transp->base.port_obj);
mb_port_tmr_delete(transp->base.port_obj);
mb_port_evt_delete(transp->base.port_obj);
}
CRITICAL_SECTION_CLOSE(inst->lock);
free(transp);
return true;
}
static void mbs_rtu_transp_start(mb_trans_base_t *inst)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
transp->state = MB_RTU_STATE_INIT;
CRITICAL_SECTION(inst->lock) {
mb_port_ser_enable(inst->port_obj);
//mb_port_tmr_enable(inst->port_obj);
};
(void)mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
}
static void mbs_rtu_transp_stop(mb_trans_base_t *inst)
{
CRITICAL_SECTION(inst->lock) {
mb_port_ser_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbs_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *prcv_addr, uint8_t **ppframe_buf, uint16_t *pbuf_len)
{
if (!pbuf_len || !prcv_addr || !ppframe_buf || !pbuf_len) {
return MB_EIO;
}
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
mb_err_enum_t status = MB_ENOERR;
uint8_t *pbuf = (uint8_t *)transp->rcv_buf;
uint16_t length = *pbuf_len;
if (mb_port_ser_recv_data(inst->port_obj, &pbuf, &length) == false){
*pbuf_len = 0;
return MB_EPORTERR;
}
assert(length < MB_RTU_SER_PDU_SIZE_MAX);
assert(pbuf);
/* Check length and CRC checksum */
if ((length >= MB_RTU_SER_PDU_SIZE_MIN)
&& (mb_crc16((uint8_t *)pbuf, length) == 0)) {
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*prcv_addr = pbuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and CRC checksum.
*/
*pbuf_len = (uint16_t)(length - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);
transp->rcv_buf_pos = length;
/* Return the start of the Modbus PDU to the caller. */
*ppframe_buf = (uint8_t *)&pbuf[MB_SER_PDU_PDU_OFF];
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbs_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t frame_len)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
mb_err_enum_t status = MB_ENOERR;
uint16_t crc16 = 0;
if (slv_addr > MB_MASTER_TOTAL_SLAVE_NUM) {
return MB_EINVAL;
}
if (frame_ptr && frame_len) {
/* First byte before the Modbus-PDU is the slave address. */
transp->snd_buf_cur = (uint8_t *)frame_ptr - 1;
transp->snd_buf_cnt = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
transp->snd_buf_cur[MB_SER_PDU_ADDR_OFF] = slv_addr;
transp->snd_buf_cnt += frame_len;
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
crc16 = mb_crc16((uint8_t *) transp->snd_buf_cur, transp->snd_buf_cnt);
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 & 0xFF);
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 >> 8);
bool ret = mb_port_ser_send_data(inst->port_obj, (uint8_t *)transp->snd_buf_cur, transp->snd_buf_cnt);
if (!ret) {
return MB_EPORTERR;
}
} else {
status = MB_EIO;
}
return status;
}
__attribute__((unused))
static bool mbs_rtu_transp_rcv_fsm(mb_trans_base_t *inst)
{
return false;
}
__attribute__((unused))
static bool mbs_rtu_transp_snd_fsm(mb_trans_base_t *inst)
{
return false;
}
IRAM_ATTR
static bool mbs_rtu_transp_tmr_35_expired(void *inst)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
bool need_poll = false;
//mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(transp->base.port_obj);
mb_port_tmr_disable(transp->base.port_obj);
return need_poll;
}
static void mbs_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->snd_buf[MB_RTU_SER_PDU_PDU_OFF];
}
}
void mbs_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = (uint8_t *)&transp->rcv_buf[MB_PDU_FUNC_OFF];
}
}
__attribute__((unused))
static uint16_t mbs_rtu_transp_get_snd_len(mb_trans_base_t *inst)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
return transp->snd_buf_cnt;
}
__attribute__((unused))
static void mbs_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len)
{
mbs_rtu_transp_t *transp = __containerof(inst, mbs_rtu_transp_t, base);
CRITICAL_SECTION(inst->lock) {
transp->snd_buf_cnt = snd_pdu_len;
}
}
#endif

View File

@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "mb_config.h"
#include "mb_common.h"
#include "mb_types.h"
#include "mb_frame.h"
#include "mb_proto.h"
#include "mbcrc.h"
#include "transport_common.h"
#include "port_common.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_RTU_EN)
/* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
* The timer reload value for a character is given by:
*
* ChTimeValue = Ticks_per_1s / (Baudrate / 11)
* = 11 * Ticks_per_1s / Baudrate
* = 220000 / Baudrate
* The reload for t3.5 is 1.5 times this value and similary
* for t3.5.
*/
#define MB_RTU_GET_T35_VAL(baudrate) (__extension__( \
{ \
uint16_t tmr_35_50us = (baudrate > 19200) ? \
35 : ((7UL * 220000UL) / (2UL * baudrate)); \
tmr_35_50us; \
} \
))
/* ----------------------- Defines ------------------------------------------*/
#define MB_RTU_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */
#define MB_RTU_SER_PDU_SIZE_MAX MB_BUFFER_SIZE /*!< Maximum size of a Modbus RTU frame. */
#define MB_RTU_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */
#define MB_RTU_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_RTU_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
typedef enum
{
MB_RTU_STATE_INIT, /*!< Receiver is in initial state. */
MB_RTU_STATE_ACTIVE, /*!< Receiver is in active state. */
MB_RTU_STATE_ERROR /*!< If the frame is invalid. */
} mb_rtu_state_enum_t;
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct mb_trans_base_t mb_trans_base_t;
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
mb_err_enum_t mbs_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
bool mbm_rtu_transp_delete(mb_trans_base_t *inst);
bool mbs_rtu_transp_delete(mb_trans_base_t *inst);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,218 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tcp_transport.h"
#include "port_tcp_common.h"
#include "port_tcp_master.h" // for port tout function
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
static const char *TAG = "mb_transp.tcp_master";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
uint8_t recv_buf[MB_TCP_BUF_SIZE];
uint8_t send_buf[MB_TCP_BUF_SIZE];
mb_tcp_state_enum_t state;
uint16_t snd_pdu_len;
} mbm_tcp_transp_t;
/* ----------------------- Defines ------------------------------------------*/
/* ----------------------- Function prototypes ------------------------------*/
mb_err_enum_t mbm_tcp_transp_create(mb_tcp_opts_t *tcp_opts, void **in_out_inst);
static void mbm_tcp_transp_start(mb_trans_base_t *inst);
static void mbm_tcp_transp_stop(mb_trans_base_t *inst);
static mb_err_enum_t mbm_tcp_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *pbuf_len);
static mb_err_enum_t mbm_tcp_transp_send(mb_trans_base_t *inst, uint8_t _unused, const uint8_t *pframe, uint16_t len);
static void mbm_tcp_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static void mbm_tcp_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
bool mbm_tcp_transp_delete(mb_trans_base_t *inst);
static bool mbm_tcp_transp_rq_is_bcast(mb_trans_base_t *inst);
//static bool mbm_tcp_transp_tmr_expired(void *inst);
mb_err_enum_t mbm_tcp_transp_create(mb_tcp_opts_t *tcp_opts, void **in_out_inst)
{
mb_err_enum_t ret = MB_ENOERR;
mbm_tcp_transp_t *transp = NULL;
transp = (mbm_tcp_transp_t *)calloc(1, sizeof(mbm_tcp_transp_t));
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for instance.");
CRITICAL_SECTION_INIT(transp->base.lock);
CRITICAL_SECTION_LOCK(transp->base.lock);
transp->base.frm_rcv = mbm_tcp_transp_receive;
transp->base.frm_send = mbm_tcp_transp_send;
transp->base.frm_start = mbm_tcp_transp_start;
transp->base.frm_stop = mbm_tcp_transp_stop;
transp->base.get_rx_frm = mbm_tcp_transp_get_rcv_buf;
transp->base.get_tx_frm = mbm_tcp_transp_get_snd_buf;
transp->base.frm_delete = mbm_tcp_transp_delete;
transp->base.frm_is_bcast = mbm_tcp_transp_rq_is_bcast;
// Copy parent object descriptor
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mbm_port_tcp_create(tcp_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, MB_TCP_TIMEOUT_MS * MB_TIMER_TICS_PER_MS);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "timer port creation, err: %d", ret);
// Override default response time if defined
if (tcp_opts->response_tout_ms) {
mb_port_tmr_set_response_time(port_obj, tcp_opts->response_tout_ms);
}
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "event port creation, err: %d", ret);
// Set callback function pointer for the timer
// port_obj->cb.tmr_expired = mbm_tcp_transp_tmr_expired;
// port_obj->cb.tx_empty = NULL;
// port_obj->cb.byte_rcvd = NULL;
// port_obj->arg = (void *)transp;
transp->base.port_obj = port_obj;
transp->port_obj = port_obj;
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
return MB_ENOERR;
error:
if (port_obj) {
free(port_obj->event_obj);
free(port_obj->timer_obj);
}
free(port_obj);
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbm_tcp_transp_delete(mb_trans_base_t *inst)
{
mbm_tcp_transp_t *transp = __containerof(inst, mbm_tcp_transp_t, base);
// destroy method of port tcp master is here
CRITICAL_SECTION(inst->lock) {
mbm_port_tcp_delete(inst->port_obj);
mb_port_tmr_delete(inst->port_obj);
mb_port_evt_delete(inst->port_obj);
}
CRITICAL_SECTION_CLOSE(inst->lock);
free(transp);
return true;
}
static void mbm_tcp_transp_start(mb_trans_base_t *inst)
{
mbm_tcp_transp_t *transp = __containerof(inst, mbm_tcp_transp_t, base);
transp->state = MB_TCP_STATE_INIT;
CRITICAL_SECTION(inst->lock) {
mbm_port_tcp_enable(inst->port_obj);
mb_port_tmr_enable(inst->port_obj);
};
/* No special startup required for TCP. */
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_READY));
}
static void mbm_tcp_transp_stop(mb_trans_base_t *inst)
{
/* Make sure that no more clients are connected. */
CRITICAL_SECTION(inst->lock) {
mbm_port_tcp_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbm_tcp_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr, uint8_t **frame_ptr_buf, uint16_t *pbuf_len)
{
mb_err_enum_t status = MB_EIO;
uint8_t *frame_ptr;
uint16_t len = *pbuf_len;
uint16_t pid;
if (mbm_port_tcp_recv_data(inst->port_obj, &frame_ptr, &len) != false) {
pid = frame_ptr[MB_TCP_PID] << 8U;
pid |= frame_ptr[MB_TCP_PID + 1];
if (pid == MB_TCP_PROTOCOL_ID) {
*frame_ptr_buf = &frame_ptr[MB_TCP_FUNC];
*pbuf_len = len - MB_TCP_FUNC;
status = MB_ENOERR;
/* Get MBAP UID field if its support is enabled.
* Otherwise just ignore this field.
*/
#if MB_TCP_UID_ENABLED
*rcv_addr = frame_ptr[MB_TCP_UID];
#else
*rcv_addr = MB_TCP_PSEUDO_ADDRESS;
#endif
}
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbm_tcp_transp_send(mb_trans_base_t *inst, uint8_t address, const uint8_t *pframe, uint16_t len)
{
mb_err_enum_t status = MB_ENOERR;
uint8_t *frame_ptr = (uint8_t *)pframe - MB_TCP_FUNC;
uint16_t tcp_len = len + MB_TCP_FUNC;
/* The MBAP header is already initialized because the caller calls this
* function with the buffer returned by the previous call. Therefore we
* only have to update the length in the header. Note that the length
* header includes the size of the Modbus PDU and the UID Byte. Therefore
* the length is len plus one.
*/
frame_ptr[MB_TCP_LEN] = (len + 1) >> 8U;
frame_ptr[MB_TCP_LEN + 1] = (len + 1) & 0xFF;
/* Set UID field in the MBAP if it is supported.
* If the RTU over TCP is not supported, the UID = 0 or 0xFF.
*/
#if MB_TCP_UID_ENABLED
frame_ptr[MB_TCP_UID] = address;
#else
frame_ptr[MB_TCP_UID] = 0x00;
#endif
if (mbm_port_tcp_send_data(inst->port_obj, address, frame_ptr, tcp_len) == false) {
status = MB_EIO;
}
mb_port_tmr_respond_timeout_enable(inst->port_obj);
return status;
}
// IRAM_ATTR
// static bool mbm_tcp_transp_tmr_expired(void *inst)
// {
// mbm_tcp_transp_t *transp = __containerof(inst, mbm_tcp_transp_t, base);
// return mbm_port_timer_expired(transp->base.port_obj);
// }
static void mbm_tcp_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_tcp_transp_t *transp = __containerof(inst, mbm_tcp_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = transp->recv_buf + MB_TCP_FUNC;
}
}
static void mbm_tcp_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbm_tcp_transp_t *transp = __containerof(inst, mbm_tcp_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = transp->send_buf + MB_TCP_FUNC;
}
}
static bool mbm_tcp_transp_rq_is_bcast(mb_trans_base_t *inst)
{
return false; //no broadcast packets on tcp
}
#endif

View File

@@ -0,0 +1,225 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tcp_transport.h"
#include "port_tcp_common.h"
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
static const char *TAG = "mb_transp.tcp_slave";
typedef struct
{
mb_trans_base_t base;
mb_port_base_t *port_obj;
uint8_t recv_buf[MB_TCP_BUF_SIZE];
uint8_t send_buf[MB_TCP_BUF_SIZE];
mb_tcp_state_enum_t state;
uint16_t snd_pdu_len;
} mbs_tcp_transp_t;
/* ----------------------- Defines ------------------------------------------*/
/* ----------------------- Function prototypes ------------------------------*/
static void mbs_tcp_transp_start(mb_trans_base_t *inst);
static void mbs_tcp_transp_stop(mb_trans_base_t *inst);
static mb_err_enum_t mbs_tcp_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr, uint8_t **frame_ptr_buf, uint16_t *pbuf_len);
static mb_err_enum_t mbs_tcp_transp_send(mb_trans_base_t *inst, uint8_t _unused, const uint8_t *frame_ptr, uint16_t len);
static void mbs_tcp_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
static void mbs_tcp_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
bool mbs_tcp_transp_delete(mb_trans_base_t *inst);
static bool mbs_tcp_transp_tmr_expired(void *inst);
mb_err_enum_t mbs_tcp_transp_create(mb_tcp_opts_t *tcp_opts, void **in_out_inst)
{
mb_err_enum_t ret = MB_ENOERR;
mbs_tcp_transp_t *transp = NULL;
transp = (mbs_tcp_transp_t *)calloc(1, sizeof(mbs_tcp_transp_t));
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for instance.");
CRITICAL_SECTION_INIT(transp->base.lock);
CRITICAL_SECTION_LOCK(transp->base.lock);
transp->base.frm_rcv = mbs_tcp_transp_receive;
transp->base.frm_send = mbs_tcp_transp_send;
transp->base.frm_start = mbs_tcp_transp_start;
transp->base.frm_stop = mbs_tcp_transp_stop;
transp->base.get_rx_frm = mbs_tcp_transp_get_rcv_buf;
transp->base.get_tx_frm = mbs_tcp_transp_get_snd_buf;
transp->base.frm_delete = mbs_tcp_transp_delete;
transp->base.frm_is_bcast = NULL;
// Copy parent object descriptor
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
transp->base.descr.obj_name = (char *)TAG;
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
ret = mbs_port_tcp_create(tcp_opts, &port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "tcp port creation, err: %d", ret);
ret = mb_port_tmr_create(port_obj, MB_TCP_TIMEOUT_MS * MB_TIMER_TICS_PER_MS);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "timer port creation, err: %d", ret);
// Override default response time if defined
if (tcp_opts->response_tout_ms) {
mb_port_tmr_set_response_time(port_obj, tcp_opts->response_tout_ms);
}
ret = mb_port_evt_create(port_obj);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EPORTERR, error, TAG, "event port creation, err: %d", ret);
transp->base.port_obj = port_obj;
// Set callback function pointer for the timer
port_obj->cb.tmr_expired = mbs_tcp_transp_tmr_expired;
port_obj->cb.tx_empty = NULL;
port_obj->cb.byte_rcvd = NULL;
port_obj->arg = (void *)transp;
transp->port_obj = port_obj;
*in_out_inst = &(transp->base);
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
CRITICAL_SECTION_UNLOCK(transp->base.lock);
return MB_ENOERR;
error:
if (port_obj) {
free(port_obj->event_obj);
free(port_obj->timer_obj);
}
free(port_obj);
CRITICAL_SECTION_CLOSE(transp->base.lock);
free(transp);
return ret;
}
bool mbs_tcp_transp_delete(mb_trans_base_t *inst)
{
mbs_tcp_transp_t *transp = __containerof(inst, mbs_tcp_transp_t, base);
// destroy method of port tcp slave is here
CRITICAL_SECTION(inst->lock) {
mbs_port_tcp_delete(inst->port_obj);
mb_port_tmr_delete(inst->port_obj);
mb_port_evt_delete(inst->port_obj);
}
CRITICAL_SECTION_CLOSE(inst->lock);
free(transp);
return true;
}
static void mbs_tcp_transp_start(mb_trans_base_t *inst)
{
CRITICAL_SECTION(inst->lock) {
mbs_port_tcp_enable(inst->port_obj);
mb_port_tmr_enable(inst->port_obj);
};
/* No special startup required for TCP. */
(void)mb_port_evt_post(inst->port_obj, EVENT(EV_READY));
}
static void mbs_tcp_transp_stop(mb_trans_base_t *inst)
{
/* Make sure that no more clients are connected. */
CRITICAL_SECTION(inst->lock) {
mbs_port_tcp_disable(inst->port_obj);
mb_port_tmr_disable(inst->port_obj);
};
}
static mb_err_enum_t mbs_tcp_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr, uint8_t **frame_ptr_buf, uint16_t *pbuf_len)
{
mb_err_enum_t status = MB_EIO;
uint8_t *frame_ptr;
uint16_t len = *pbuf_len;
uint16_t pid;
if (mbs_port_tcp_recv_data(inst->port_obj, &frame_ptr, &len) != false) {
pid = frame_ptr[MB_TCP_PID] << 8U;
pid |= frame_ptr[MB_TCP_PID + 1];
if (pid == MB_TCP_PROTOCOL_ID) {
*frame_ptr_buf = &frame_ptr[MB_TCP_FUNC];
*pbuf_len = len - MB_TCP_FUNC;
status = MB_ENOERR;
/* Get MBAP UID field if its support is enabled.
* Otherwise just ignore this field.
*/
#if MB_TCP_UID_ENABLED
*rcv_addr = frame_ptr[MB_TCP_UID];
#else
*rcv_addr = MB_TCP_PSEUDO_ADDRESS;
#endif
}
} else {
status = MB_EIO;
}
return status;
}
static mb_err_enum_t mbs_tcp_transp_send(mb_trans_base_t *inst, uint8_t _unused, const uint8_t *pframe, uint16_t len)
{
mb_err_enum_t status = MB_ENOERR;
uint8_t *frame_ptr = (uint8_t *)pframe - MB_TCP_FUNC;
uint16_t tcp_len = len + MB_TCP_FUNC;
/* The MBAP header is already initialized because the caller calls this
* function with the buffer returned by the previous call. Therefore we
* only have to update the length in the header. Note that the length
* header includes the size of the Modbus PDU and the UID Byte. Therefore
* the length is len plus one.
*/
frame_ptr[MB_TCP_LEN] = (len + 1) >> 8U;
frame_ptr[MB_TCP_LEN + 1] = (len + 1) & 0xFF;
if (mbs_port_tcp_send_data(inst->port_obj, frame_ptr, tcp_len) == false) {
status = MB_EIO;
}
return status;
}
__attribute__((unused))
static bool mbs_tcp_transp_tmr_expired(void *inst)
{
mbs_tcp_transp_t *transp = __containerof(inst, mbs_tcp_transp_t, base);
bool need_poll = false;
mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(transp->base.port_obj);
mb_port_tmr_disable(transp->base.port_obj);
switch(timer_mode) {
case MB_TMODE_T35:
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
ESP_EARLY_LOGD(TAG, "EV_READY");
break;
case MB_TMODE_RESPOND_TIMEOUT:
mb_port_evt_set_err_type(transp->base.port_obj, EV_ERROR_RESPOND_TIMEOUT);
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_ERROR_PROCESS));
ESP_EARLY_LOGD(TAG, "EV_ERROR_RESPOND_TIMEOUT");
break;
case MB_TMODE_CONVERT_DELAY:
/* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_EXECUTE));
ESP_EARLY_LOGD(TAG, "MB_TMODE_CONVERT_DELAY");
break;
default:
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
break;
}
return need_poll;
}
static void mbs_tcp_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_tcp_transp_t *transp = __containerof(inst, mbs_tcp_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = transp->recv_buf + MB_TCP_FUNC;
}
}
static void mbs_tcp_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
{
mbs_tcp_transp_t *transp = __containerof(inst, mbs_tcp_transp_t, base);
CRITICAL_SECTION(inst->lock) {
*frame_ptr_buf = transp->send_buf + MB_TCP_FUNC;
}
}
#endif

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "sdkconfig.h"
#include "mb_common.h"
#include "mb_types.h"
#include "transport_common.h"
#include "port_common.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
/* ----------------------- Defines ------------------------------------------*/
// Common definitions for TCP port
#define MB_TCP_BUF_SIZE (256 + 7) // Must hold a complete Modbus TCP frame.
#define MB_TCP_TIMEOUT_MS (1000)
typedef enum
{
MB_TCP_STATE_INIT, /*!< Receiver is in initial state. */
MB_TCP_STATE_ACTIVE, /*!< Receiver is in active state. */
MB_TCP_STATE_ERROR /*!< If the frame is invalid. */
} mb_tcp_state_enum_t;
typedef struct mb_trans_base_t mb_trans_base_t;
mb_err_enum_t mbm_tcp_transp_create(mb_tcp_opts_t *tcp_opts, void **in_out_inst);
mb_err_enum_t mbs_tcp_transp_create(mb_tcp_opts_t *tcp_opts, void **in_out_inst);
bool mbs_tcp_transp_delete(mb_trans_base_t *inst);
bool mbm_tcp_transp_delete(mb_trans_base_t *inst);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "mb_types.h"
#include "port_common.h"
#include "mb_port_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct mb_trans_base_t mb_trans_base_t; /*!< Type of moddus transport object */
typedef struct _obj_descr obj_descr_t;
typedef void (*mb_frm_start_fp)(mb_trans_base_t *transport);
typedef void (*mb_frm_stop_fp)(mb_trans_base_t *transport);
typedef mb_err_enum_t (*mb_frm_rcv_fp)(mb_trans_base_t *transport, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
typedef mb_err_enum_t (*mb_frm_snd_fp)(mb_trans_base_t *transport, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
typedef void (*mb_get_rx_frm_fp) (mb_trans_base_t *transport, uint8_t **frame_ptr_buf);
typedef void (*mb_get_tx_frm_fp) (mb_trans_base_t *transport, uint8_t **frame_ptr_buf);
typedef bool (*mb_get_fp)(mb_trans_base_t *inst);
struct mb_trans_base_t
{
obj_descr_t descr;
_lock_t lock;
mb_port_base_t *port_obj;
mb_frm_start_fp frm_start;
mb_frm_stop_fp frm_stop;
mb_get_fp frm_delete;
mb_frm_snd_fp frm_send;
mb_frm_rcv_fp frm_rcv;
mb_get_rx_frm_fp get_rx_frm;
mb_get_rx_frm_fp get_tx_frm;
mb_get_fp frm_is_bcast;
}; //!< Transport methods
#ifdef __cplusplus
}
#endif

50
pytest.ini Normal file
View File

@@ -0,0 +1,50 @@
[pytest]
# only the files with prefix `pytest_` would be recognized as pytest test scripts.
python_files = pytest_*.py
norecursedirs = managed_components* espressif__mdns*
# ignore PytestExperimentalApiWarning for record_xml_attribute
# set traceback to "short" to prevent the overwhelming tracebacks
addopts =
-s
--embedded-services esp,idf
--tb short
--skip-check-coredump y
--ignore-glob=pytest_*mdns*.py
# ignore DeprecationWarning
filterwarnings =
ignore::DeprecationWarning:matplotlib.*:
ignore::DeprecationWarning:google.protobuf.*:
ignore::_pytest.warning_types.PytestExperimentalApiWarning
markers =
# target markers
esp32: support esp32 target
esp32s2: support esp32s2 target
esp32s3: support esp32s3 target
esp32c3: support esp32c3 target
esp32c2: support esp32c2 target
# env markers
generic: tests should be run on generic runners
# multi-dut markers
multi_dut_generic: tests should be run on generic runners, at least have two duts connected.
multi_dut_modbus_tcp: Modbus TCP runners with two duts connected
multi_dut_modbus_rs485: Modbus RTU/ASCII runners with two duts connected
multi_dut_modbus_serial: Alias for Modbus RTU/ASCII runners with two duts connected
generic_multi_device: generic multi device runners
# log related
log_cli = True
log_cli_level = INFO
log_cli_format = %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
# junit related
junit_family = xunit1
## log all to `system-out` when case fail
junit_logging = stdout
junit_log_passing_tests = False

View File

@@ -0,0 +1,16 @@
library/error\.o
/.*error\S*\.o
.*error.*\.c\.obj
.*error.*\.c
.*error.*\.cpp\.obj
.*error.*\.cxx\.obj
.*error.*\.cc\.obj
-Werror
error\.d
/.*error\S*.d
reassigning to symbol
changes choice state
crosstool_version_check\.cmake
CryptographyDeprecationWarning
Warning: \d+/\d+ app partitions are too small for binary
CMake Deprecation Warning at main/lib/tinyxml2/CMakeLists\.txt:11 \(cmake_policy\)