forked from espressif/esp-modbus
add modbus component files
This commit is contained in:
+55
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 Espressif’s IoT Development Framework, [ESP_IDF](https://github.com/espressif/esp-idf). The packages from this repository are uploaded to Espressif’s component repository.
|
||||
|
||||
* [esp-modbus component in component repository](https://components.espressif.com/component/espressif/esp-modbus)
|
||||
|
||||
You can add the component to your project via `idf.py add-dependency`. More information about idf-component-manager can be found in [Espressif API guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html) or [PyPi registry](https://pypi.org/project/idf-component-manager).
|
||||
|
||||
The ESP-Modbus library can be used with ESP-IDF 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.
|
||||
@@ -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) \
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, ®_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, ®_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, ®_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, ®_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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, ®_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, ®_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, ®_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, ®_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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 $
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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\)
|
||||
Reference in New Issue
Block a user