From 1a5c957e224fd31d5d9752189720b972eac7e2a2 Mon Sep 17 00:00:00 2001 From: aleks Date: Sun, 10 Dec 2023 15:10:43 +0100 Subject: [PATCH] add modbus component files --- .gitignore | 55 + .gitlab-ci.yml | 300 +++++ .pre-commit-config.yaml | 19 + CMakeLists.txt | 85 ++ Kconfig | 209 +++ LICENSE | 202 +++ README.md | 63 + component.mk | 29 + idf_component.yml | 11 + linker.lf | 23 + .../common/esp_modbus_callbacks.h | 24 + .../mb_controller/common/esp_modbus_master.c | 438 +++++++ .../common/esp_modbus_master_serial.c | 38 + .../common/esp_modbus_master_tcp.c | 28 + .../mb_controller/common/esp_modbus_slave.c | 517 ++++++++ .../common/esp_modbus_slave_serial.c | 41 + .../common/esp_modbus_slave_tcp.c | 30 + .../common/include/esp_modbus_common.h | 167 +++ .../common/include/esp_modbus_master.h | 411 ++++++ .../common/include/esp_modbus_slave.h | 276 ++++ .../common/include/mbcontroller.h | 20 + modbus/mb_controller/common/mbc_master.h | 89 ++ modbus/mb_controller/common/mbc_slave.h | 79 ++ .../mb_controller/serial/mbc_serial_master.c | 683 ++++++++++ .../mb_controller/serial/mbc_serial_master.h | 39 + .../mb_controller/serial/mbc_serial_slave.c | 292 +++++ .../mb_controller/serial/mbc_serial_slave.h | 41 + modbus/mb_controller/tcp/mbc_tcp_master.c | 799 ++++++++++++ modbus/mb_controller/tcp/mbc_tcp_master.h | 34 + modbus/mb_controller/tcp/mbc_tcp_slave.c | 273 ++++ modbus/mb_controller/tcp/mbc_tcp_slave.h | 29 + modbus/mb_objects/functions/mbfunccoils.c | 224 ++++ .../mb_objects/functions/mbfunccoils_master.c | 335 +++++ modbus/mb_objects/functions/mbfuncdiag.c | 29 + modbus/mb_objects/functions/mbfuncdisc.c | 105 ++ .../mb_objects/functions/mbfuncdisc_master.c | 141 +++ modbus/mb_objects/functions/mbfuncholding.c | 271 ++++ .../functions/mbfuncholding_master.c | 477 +++++++ modbus/mb_objects/functions/mbfuncinput.c | 100 ++ .../mb_objects/functions/mbfuncinput_master.c | 134 ++ modbus/mb_objects/functions/mbfuncother.c | 68 + modbus/mb_objects/functions/mbutils.c | 128 ++ modbus/mb_objects/include/mb_callbacks.h | 31 + modbus/mb_objects/include/mb_common.h | 174 +++ modbus/mb_objects/include/mb_config.h | 184 +++ modbus/mb_objects/include/mb_frame.h | 69 + modbus/mb_objects/include/mb_func.h | 69 + modbus/mb_objects/include/mb_master.h | 28 + modbus/mb_objects/include/mb_port_types.h | 104 ++ modbus/mb_objects/include/mb_proto.h | 65 + modbus/mb_objects/include/mb_slave.h | 19 + modbus/mb_objects/include/mb_types.h | 141 +++ modbus/mb_objects/include/mb_utils.h | 123 ++ modbus/mb_objects/mb_master.c | 554 ++++++++ modbus/mb_objects/mb_slave.c | 384 ++++++ modbus/mb_ports/common/port_common.h | 204 +++ modbus/mb_ports/common/port_event.c | 209 +++ modbus/mb_ports/common/port_other.c | 129 ++ modbus/mb_ports/common/port_timer.c | 196 +++ modbus/mb_ports/serial/port_serial.c | 349 ++++++ modbus/mb_ports/serial/port_serial_common.h | 37 + modbus/mb_ports/tcp/port_tcp_common.h | 54 + modbus/mb_ports/tcp/port_tcp_driver.c | 1115 +++++++++++++++++ modbus/mb_ports/tcp/port_tcp_driver.h | 296 +++++ modbus/mb_ports/tcp/port_tcp_master.c | 259 ++++ modbus/mb_ports/tcp/port_tcp_master.h | 35 + modbus/mb_ports/tcp/port_tcp_slave.c | 73 ++ modbus/mb_ports/tcp/port_tcp_utils.c | 718 +++++++++++ modbus/mb_ports/tcp/port_tcp_utils.h | 106 ++ modbus/mb_transports/ascii/ascii_lrc.c | 92 ++ modbus/mb_transports/ascii/ascii_lrc.h | 20 + modbus/mb_transports/ascii/ascii_master.c | 288 +++++ modbus/mb_transports/ascii/ascii_slave.c | 238 ++++ modbus/mb_transports/ascii/ascii_transport.h | 43 + modbus/mb_transports/rtu/mbcrc.c | 103 ++ modbus/mb_transports/rtu/mbcrc.h | 41 + modbus/mb_transports/rtu/rtu_master.c | 299 +++++ modbus/mb_transports/rtu/rtu_slave.c | 255 ++++ modbus/mb_transports/rtu/rtu_transport.h | 68 + modbus/mb_transports/tcp/tcp_master.c | 218 ++++ modbus/mb_transports/tcp/tcp_slave.c | 225 ++++ modbus/mb_transports/tcp/tcp_transport.h | 46 + modbus/mb_transports/transport_common.h | 46 + pytest.ini | 50 + tools/ignore_build_warnings.txt | 16 + 85 files changed, 15129 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CMakeLists.txt create mode 100644 Kconfig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 component.mk create mode 100644 idf_component.yml create mode 100644 linker.lf create mode 100644 modbus/mb_controller/common/esp_modbus_callbacks.h create mode 100644 modbus/mb_controller/common/esp_modbus_master.c create mode 100644 modbus/mb_controller/common/esp_modbus_master_serial.c create mode 100644 modbus/mb_controller/common/esp_modbus_master_tcp.c create mode 100644 modbus/mb_controller/common/esp_modbus_slave.c create mode 100644 modbus/mb_controller/common/esp_modbus_slave_serial.c create mode 100644 modbus/mb_controller/common/esp_modbus_slave_tcp.c create mode 100644 modbus/mb_controller/common/include/esp_modbus_common.h create mode 100644 modbus/mb_controller/common/include/esp_modbus_master.h create mode 100644 modbus/mb_controller/common/include/esp_modbus_slave.h create mode 100644 modbus/mb_controller/common/include/mbcontroller.h create mode 100644 modbus/mb_controller/common/mbc_master.h create mode 100644 modbus/mb_controller/common/mbc_slave.h create mode 100644 modbus/mb_controller/serial/mbc_serial_master.c create mode 100644 modbus/mb_controller/serial/mbc_serial_master.h create mode 100644 modbus/mb_controller/serial/mbc_serial_slave.c create mode 100644 modbus/mb_controller/serial/mbc_serial_slave.h create mode 100644 modbus/mb_controller/tcp/mbc_tcp_master.c create mode 100644 modbus/mb_controller/tcp/mbc_tcp_master.h create mode 100644 modbus/mb_controller/tcp/mbc_tcp_slave.c create mode 100644 modbus/mb_controller/tcp/mbc_tcp_slave.h create mode 100644 modbus/mb_objects/functions/mbfunccoils.c create mode 100644 modbus/mb_objects/functions/mbfunccoils_master.c create mode 100644 modbus/mb_objects/functions/mbfuncdiag.c create mode 100644 modbus/mb_objects/functions/mbfuncdisc.c create mode 100644 modbus/mb_objects/functions/mbfuncdisc_master.c create mode 100644 modbus/mb_objects/functions/mbfuncholding.c create mode 100644 modbus/mb_objects/functions/mbfuncholding_master.c create mode 100644 modbus/mb_objects/functions/mbfuncinput.c create mode 100644 modbus/mb_objects/functions/mbfuncinput_master.c create mode 100644 modbus/mb_objects/functions/mbfuncother.c create mode 100644 modbus/mb_objects/functions/mbutils.c create mode 100644 modbus/mb_objects/include/mb_callbacks.h create mode 100644 modbus/mb_objects/include/mb_common.h create mode 100644 modbus/mb_objects/include/mb_config.h create mode 100644 modbus/mb_objects/include/mb_frame.h create mode 100644 modbus/mb_objects/include/mb_func.h create mode 100644 modbus/mb_objects/include/mb_master.h create mode 100644 modbus/mb_objects/include/mb_port_types.h create mode 100644 modbus/mb_objects/include/mb_proto.h create mode 100644 modbus/mb_objects/include/mb_slave.h create mode 100644 modbus/mb_objects/include/mb_types.h create mode 100644 modbus/mb_objects/include/mb_utils.h create mode 100644 modbus/mb_objects/mb_master.c create mode 100644 modbus/mb_objects/mb_slave.c create mode 100644 modbus/mb_ports/common/port_common.h create mode 100644 modbus/mb_ports/common/port_event.c create mode 100644 modbus/mb_ports/common/port_other.c create mode 100644 modbus/mb_ports/common/port_timer.c create mode 100644 modbus/mb_ports/serial/port_serial.c create mode 100644 modbus/mb_ports/serial/port_serial_common.h create mode 100644 modbus/mb_ports/tcp/port_tcp_common.h create mode 100644 modbus/mb_ports/tcp/port_tcp_driver.c create mode 100644 modbus/mb_ports/tcp/port_tcp_driver.h create mode 100644 modbus/mb_ports/tcp/port_tcp_master.c create mode 100644 modbus/mb_ports/tcp/port_tcp_master.h create mode 100644 modbus/mb_ports/tcp/port_tcp_slave.c create mode 100644 modbus/mb_ports/tcp/port_tcp_utils.c create mode 100644 modbus/mb_ports/tcp/port_tcp_utils.h create mode 100644 modbus/mb_transports/ascii/ascii_lrc.c create mode 100644 modbus/mb_transports/ascii/ascii_lrc.h create mode 100644 modbus/mb_transports/ascii/ascii_master.c create mode 100644 modbus/mb_transports/ascii/ascii_slave.c create mode 100644 modbus/mb_transports/ascii/ascii_transport.h create mode 100644 modbus/mb_transports/rtu/mbcrc.c create mode 100644 modbus/mb_transports/rtu/mbcrc.h create mode 100644 modbus/mb_transports/rtu/rtu_master.c create mode 100644 modbus/mb_transports/rtu/rtu_slave.c create mode 100644 modbus/mb_transports/rtu/rtu_transport.h create mode 100644 modbus/mb_transports/tcp/tcp_master.c create mode 100644 modbus/mb_transports/tcp/tcp_slave.c create mode 100644 modbus/mb_transports/tcp/tcp_transport.h create mode 100644 modbus/mb_transports/transport_common.h create mode 100644 pytest.ini create mode 100644 tools/ignore_build_warnings.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d7bf69 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..5d4173d --- /dev/null +++ b/.gitlab-ci.yml @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cb32015 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..808129f --- /dev/null +++ b/CMakeLists.txt @@ -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}") diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..1fd6dc4 --- /dev/null +++ b/Kconfig @@ -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 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 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1230531 --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/component.mk b/component.mk new file mode 100644 index 0000000..a596857 --- /dev/null +++ b/component.mk @@ -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) \ + ) + + + diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..cc528a0 --- /dev/null +++ b/idf_component.yml @@ -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" diff --git a/linker.lf b/linker.lf new file mode 100644 index 0000000..3f6c2a2 --- /dev/null +++ b/linker.lf @@ -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) + diff --git a/modbus/mb_controller/common/esp_modbus_callbacks.h b/modbus/mb_controller/common/esp_modbus_callbacks.h new file mode 100644 index 0000000..831375b --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_callbacks.h @@ -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 diff --git a/modbus/mb_controller/common/esp_modbus_master.c b/modbus/mb_controller/common/esp_modbus_master.c new file mode 100644 index 0000000..5581848 --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_master.c @@ -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; +} diff --git a/modbus/mb_controller/common/esp_modbus_master_serial.c b/modbus/mb_controller/common/esp_modbus_master_serial.c new file mode 100644 index 0000000..967c4ba --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_master_serial.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_controller/common/esp_modbus_master_tcp.c b/modbus/mb_controller/common/esp_modbus_master_tcp.c new file mode 100644 index 0000000..9a7a5f6 --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_master_tcp.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_controller/common/esp_modbus_slave.c b/modbus/mb_controller/common/esp_modbus_slave.c new file mode 100644 index 0000000..5aac55a --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_slave.c @@ -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; +} diff --git a/modbus/mb_controller/common/esp_modbus_slave_serial.c b/modbus/mb_controller/common/esp_modbus_slave_serial.c new file mode 100644 index 0000000..b28cc26 --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_slave_serial.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_controller/common/esp_modbus_slave_tcp.c b/modbus/mb_controller/common/esp_modbus_slave_tcp.c new file mode 100644 index 0000000..9a1945a --- /dev/null +++ b/modbus/mb_controller/common/esp_modbus_slave_tcp.c @@ -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 diff --git a/modbus/mb_controller/common/include/esp_modbus_common.h b/modbus/mb_controller/common/include/esp_modbus_common.h new file mode 100644 index 0000000..364a115 --- /dev/null +++ b/modbus/mb_controller/common/include/esp_modbus_common.h @@ -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 + +#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 + diff --git a/modbus/mb_controller/common/include/esp_modbus_master.h b/modbus/mb_controller/common/include/esp_modbus_master.h new file mode 100644 index 0000000..09f8e03 --- /dev/null +++ b/modbus/mb_controller/common/include/esp_modbus_master.h @@ -0,0 +1,411 @@ +/* + * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include // for standard int types definition +#include // 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 + diff --git a/modbus/mb_controller/common/include/esp_modbus_slave.h b/modbus/mb_controller/common/include/esp_modbus_slave.h new file mode 100644 index 0000000..1db378d --- /dev/null +++ b/modbus/mb_controller/common/include/esp_modbus_slave.h @@ -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 // for standard int types definition +#include // 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 diff --git a/modbus/mb_controller/common/include/mbcontroller.h b/modbus/mb_controller/common/include/mbcontroller.h new file mode 100644 index 0000000..c873713 --- /dev/null +++ b/modbus/mb_controller/common/include/mbcontroller.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include // for standard int types definition +#include // 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" + + diff --git a/modbus/mb_controller/common/mbc_master.h b/modbus/mb_controller/common/mbc_master.h new file mode 100644 index 0000000..c0e1900 --- /dev/null +++ b/modbus/mb_controller/common/mbc_master.h @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include // 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 diff --git a/modbus/mb_controller/common/mbc_slave.h b/modbus/mb_controller/common/mbc_slave.h new file mode 100644 index 0000000..c0e5207 --- /dev/null +++ b/modbus/mb_controller/common/mbc_slave.h @@ -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 diff --git a/modbus/mb_controller/serial/mbc_serial_master.c b/modbus/mb_controller/serial/mbc_serial_master.c new file mode 100644 index 0000000..5a2292f --- /dev/null +++ b/modbus/mb_controller/serial/mbc_serial_master.c @@ -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 // for calculation of time stamp in milliseconds +#include "esp_log.h" // for log_write +#include // 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 \ No newline at end of file diff --git a/modbus/mb_controller/serial/mbc_serial_master.h b/modbus/mb_controller/serial/mbc_serial_master.h new file mode 100644 index 0000000..c51ddad --- /dev/null +++ b/modbus/mb_controller/serial/mbc_serial_master.h @@ -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 // for standard int types definition +#include // 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 diff --git a/modbus/mb_controller/serial/mbc_serial_slave.c b/modbus/mb_controller/serial/mbc_serial_slave.c new file mode 100644 index 0000000..3f3a13e --- /dev/null +++ b/modbus/mb_controller/serial/mbc_serial_slave.c @@ -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 // 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 \ No newline at end of file diff --git a/modbus/mb_controller/serial/mbc_serial_slave.h b/modbus/mb_controller/serial/mbc_serial_slave.h new file mode 100644 index 0000000..9d439dc --- /dev/null +++ b/modbus/mb_controller/serial/mbc_serial_slave.h @@ -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 // for standard int types definition +#include // 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 \ No newline at end of file diff --git a/modbus/mb_controller/tcp/mbc_tcp_master.c b/modbus/mb_controller/tcp/mbc_tcp_master.c new file mode 100644 index 0000000..5fdaf44 --- /dev/null +++ b/modbus/mb_controller/tcp/mbc_tcp_master.c @@ -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 // for calculation of time stamp in milliseconds +#include "esp_log.h" // for log_write +#include // for memcpy +#include // 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 \ No newline at end of file diff --git a/modbus/mb_controller/tcp/mbc_tcp_master.h b/modbus/mb_controller/tcp/mbc_tcp_master.h new file mode 100644 index 0000000..876b851 --- /dev/null +++ b/modbus/mb_controller/tcp/mbc_tcp_master.h @@ -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 // for standard int types definition +#include // 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 diff --git a/modbus/mb_controller/tcp/mbc_tcp_slave.c b/modbus/mb_controller/tcp/mbc_tcp_slave.c new file mode 100644 index 0000000..8df9945 --- /dev/null +++ b/modbus/mb_controller/tcp/mbc_tcp_slave.c @@ -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 // 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 diff --git a/modbus/mb_controller/tcp/mbc_tcp_slave.h b/modbus/mb_controller/tcp/mbc_tcp_slave.h new file mode 100644 index 0000000..14c0b12 --- /dev/null +++ b/modbus/mb_controller/tcp/mbc_tcp_slave.h @@ -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 // for standard int types definition +#include // 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); diff --git a/modbus/mb_objects/functions/mbfunccoils.c b/modbus/mb_objects/functions/mbfunccoils.c new file mode 100644 index 0000000..156ce7f --- /dev/null +++ b/modbus/mb_objects/functions/mbfunccoils.c @@ -0,0 +1,224 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (c) 2006 Christian Walter + * 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 +#include +#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 diff --git a/modbus/mb_objects/functions/mbfunccoils_master.c b/modbus/mb_objects/functions/mbfunccoils_master.c new file mode 100644 index 0000000..351845a --- /dev/null +++ b/modbus/mb_objects/functions/mbfunccoils_master.c @@ -0,0 +1,335 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (C) 2013 Armink + * 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 diff --git a/modbus/mb_objects/functions/mbfuncdiag.c b/modbus/mb_objects/functions/mbfuncdiag.c new file mode 100644 index 0000000..9ca1d77 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncdiag.c @@ -0,0 +1,29 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2006 Christian Walter + * 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 $ + */ diff --git a/modbus/mb_objects/functions/mbfuncdisc.c b/modbus/mb_objects/functions/mbfuncdisc.c new file mode 100644 index 0000000..1f3ed98 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncdisc.c @@ -0,0 +1,105 @@ + /* + * FreeRTOS Modbus Libary: A Modbus serial implementation for FreeRTOS + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (C) 2006 Christian Walter + * + * 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 +#include +#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 diff --git a/modbus/mb_objects/functions/mbfuncdisc_master.c b/modbus/mb_objects/functions/mbfuncdisc_master.c new file mode 100644 index 0000000..78cc4e8 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncdisc_master.c @@ -0,0 +1,141 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (C) 2013 Armink + * 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 diff --git a/modbus/mb_objects/functions/mbfuncholding.c b/modbus/mb_objects/functions/mbfuncholding.c new file mode 100644 index 0000000..d874cde --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncholding.c @@ -0,0 +1,271 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (c) 2006 Christian Walter + * 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 +#include +#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 diff --git a/modbus/mb_objects/functions/mbfuncholding_master.c b/modbus/mb_objects/functions/mbfuncholding_master.c new file mode 100644 index 0000000..51bf944 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncholding_master.c @@ -0,0 +1,477 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (C) 2013 Armink + * 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 diff --git a/modbus/mb_objects/functions/mbfuncinput.c b/modbus/mb_objects/functions/mbfuncinput.c new file mode 100644 index 0000000..5a6b643 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncinput.c @@ -0,0 +1,100 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (c) 2006 Christian Walter + * 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 +#include +#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 diff --git a/modbus/mb_objects/functions/mbfuncinput_master.c b/modbus/mb_objects/functions/mbfuncinput_master.c new file mode 100644 index 0000000..7b60371 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncinput_master.c @@ -0,0 +1,134 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (C) 2013 Armink + * 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 diff --git a/modbus/mb_objects/functions/mbfuncother.c b/modbus/mb_objects/functions/mbfuncother.c new file mode 100644 index 0000000..8c02c60 --- /dev/null +++ b/modbus/mb_objects/functions/mbfuncother.c @@ -0,0 +1,68 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (c) 2006 Christian Walter + * 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 +#include +#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 diff --git a/modbus/mb_objects/functions/mbutils.c b/modbus/mb_objects/functions/mbutils.c new file mode 100644 index 0000000..3fa7683 --- /dev/null +++ b/modbus/mb_objects/functions/mbutils.c @@ -0,0 +1,128 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2016, 2017 Nucleron R&D LLC + * Copyright (c) 2006 Christian Walter + * 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 +#include +/* ----------------------- 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; +} + diff --git a/modbus/mb_objects/include/mb_callbacks.h b/modbus/mb_objects/include/mb_callbacks.h new file mode 100644 index 0000000..f76debf --- /dev/null +++ b/modbus/mb_objects/include/mb_callbacks.h @@ -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 + diff --git a/modbus/mb_objects/include/mb_common.h b/modbus/mb_objects/include/mb_common.h new file mode 100644 index 0000000..33b8474 --- /dev/null +++ b/modbus/mb_objects/include/mb_common.h @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#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 \ No newline at end of file diff --git a/modbus/mb_objects/include/mb_config.h b/modbus/mb_objects/include/mb_config.h new file mode 100644 index 0000000..a1fdf4c --- /dev/null +++ b/modbus/mb_objects/include/mb_config.h @@ -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 + +/* ----------------------- 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.
+ * + * All of these settings are available in the file mbconfig.h + */ +/*! \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 Report Slave ID + * 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 1. + */ +#define MB_FUNC_OTHER_REP_SLAVEID_BUF (32) + +/*! \brief If the Report Slave ID function should be enabled. */ +#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED (CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT) + +/*! \brief If the Read Input Registers function should be enabled. */ +#define MB_FUNC_READ_INPUT_ENABLED (1) + +/*! \brief If the Read Holding Registers function should be enabled. */ +#define MB_FUNC_READ_HOLDING_ENABLED (1) + +/*! \brief If the Write Single Register function should be enabled. */ +#define MB_FUNC_WRITE_HOLDING_ENABLED (1) + +/*! \brief If the Write Multiple registers function should be enabled. */ +#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED (1) + +/*! \brief If the Read Coils function should be enabled. */ +#define MB_FUNC_READ_COILS_ENABLED (1) + +/*! \brief If the Write Coils function should be enabled. */ +#define MB_FUNC_WRITE_COIL_ENABLED (1) + +/*! \brief If the Write Multiple Coils function should be enabled. */ +#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED (1) + +/*! \brief If the Read Discrete Inputs function should be enabled. */ +#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED (1) + +/*! \brief If the Read/Write Multiple Registers 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 diff --git a/modbus/mb_objects/include/mb_frame.h b/modbus/mb_objects/include/mb_frame.h new file mode 100644 index 0000000..9298bf2 --- /dev/null +++ b/modbus/mb_objects/include/mb_frame.h @@ -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. + * + * + * <------------------------ 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 + * + */ + +/* ----------------------- 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 diff --git a/modbus/mb_objects/include/mb_func.h b/modbus/mb_objects/include/mb_func.h new file mode 100644 index 0000000..5877769 --- /dev/null +++ b/modbus/mb_objects/include/mb_func.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#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 diff --git a/modbus/mb_objects/include/mb_master.h b/modbus/mb_objects/include/mb_master.h new file mode 100644 index 0000000..4b7ff6e --- /dev/null +++ b/modbus/mb_objects/include/mb_master.h @@ -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 \ No newline at end of file diff --git a/modbus/mb_objects/include/mb_port_types.h b/modbus/mb_objects/include/mb_port_types.h new file mode 100644 index 0000000..c84630d --- /dev/null +++ b/modbus/mb_objects/include/mb_port_types.h @@ -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 \ No newline at end of file diff --git a/modbus/mb_objects/include/mb_proto.h b/modbus/mb_objects/include/mb_proto.h new file mode 100644 index 0000000..09c7511 --- /dev/null +++ b/modbus/mb_objects/include/mb_proto.h @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#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 diff --git a/modbus/mb_objects/include/mb_slave.h b/modbus/mb_objects/include/mb_slave.h new file mode 100644 index 0000000..d16c800 --- /dev/null +++ b/modbus/mb_objects/include/mb_slave.h @@ -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 \ No newline at end of file diff --git a/modbus/mb_objects/include/mb_types.h b/modbus/mb_objects/include/mb_types.h new file mode 100644 index 0000000..7d35b48 --- /dev/null +++ b/modbus/mb_objects/include/mb_types.h @@ -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 + diff --git a/modbus/mb_objects/include/mb_utils.h b/modbus/mb_objects/include/mb_utils.h new file mode 100644 index 0000000..0ccbe0f --- /dev/null +++ b/modbus/mb_objects/include/mb_utils.h @@ -0,0 +1,123 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2006 Christian Walter + * 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 bit_offset is the LSB of the value + * values + * + * \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 diff --git a/modbus/mb_objects/mb_master.c b/modbus/mb_objects/mb_master.c new file mode 100644 index 0000000..befb50e --- /dev/null +++ b/modbus/mb_objects/mb_master.c @@ -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 diff --git a/modbus/mb_objects/mb_slave.c b/modbus/mb_objects/mb_slave.c new file mode 100644 index 0000000..92839f6 --- /dev/null +++ b/modbus/mb_objects/mb_slave.c @@ -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; +} diff --git a/modbus/mb_ports/common/port_common.h b/modbus/mb_ports/common/port_common.h new file mode 100644 index 0000000..0855735 --- /dev/null +++ b/modbus/mb_ports/common/port_common.h @@ -0,0 +1,204 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +/*----------------------- 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 \ No newline at end of file diff --git a/modbus/mb_ports/common/port_event.c b/modbus/mb_ports/common/port_event.c new file mode 100644 index 0000000..835c5a0 --- /dev/null +++ b/modbus/mb_ports/common/port_event.c @@ -0,0 +1,209 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#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; +} diff --git a/modbus/mb_ports/common/port_other.c b/modbus/mb_ports/common/port_other.c new file mode 100644 index 0000000..a8f2a05 --- /dev/null +++ b/modbus/mb_ports/common/port_other.c @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#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); + } + } +} diff --git a/modbus/mb_ports/common/port_timer.c b/modbus/mb_ports/common/port_timer.c new file mode 100644 index 0000000..91c9063 --- /dev/null +++ b/modbus/mb_ports/common/port_timer.c @@ -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; +} diff --git a/modbus/mb_ports/serial/port_serial.c b/modbus/mb_ports/serial/port_serial.c new file mode 100644 index 0000000..1479202 --- /dev/null +++ b/modbus/mb_ports/serial/port_serial.c @@ -0,0 +1,349 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#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 + diff --git a/modbus/mb_ports/serial/port_serial_common.h b/modbus/mb_ports/serial/port_serial_common.h new file mode 100644 index 0000000..db02a0f --- /dev/null +++ b/modbus/mb_ports/serial/port_serial_common.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#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 diff --git a/modbus/mb_ports/tcp/port_tcp_common.h b/modbus/mb_ports/tcp/port_tcp_common.h new file mode 100644 index 0000000..a18d0bf --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_common.h @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#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 \ No newline at end of file diff --git a/modbus/mb_ports/tcp/port_tcp_driver.c b/modbus/mb_ports/tcp/port_tcp_driver.c new file mode 100644 index 0000000..7b8aa88 --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_driver.c @@ -0,0 +1,1115 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include "errno.h" + +#include "esp_log.h" +#include "esp_check.h" +#include "esp_timer.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +#include "esp_netif.h" + +#include "port_common.h" +#include "esp_vfs_eventfd.h" +#include "port_tcp_driver.h" +#include "port_tcp_utils.h" + +#if (CONFIG_FMB_COMM_MODE_TCP_EN) + +static const char *TAG = "mbm_driver"; + +static esp_event_loop_handle_t mbm_drv_loop_handle = NULL; +static int mbm_drv_loop_inst_counter = 0; +static char msg_buffer[100]; // The buffer for event debugging (used for all instances) + +/* ================== Utils ====================== */ + +static const event_msg_t event_msg_table[] = { + MB_EVENT_TBL_IT(MB_EVENT_READY), + MB_EVENT_TBL_IT(MB_EVENT_OPEN), + MB_EVENT_TBL_IT(MB_EVENT_RESOLVE), + MB_EVENT_TBL_IT(MB_EVENT_CONNECT), + MB_EVENT_TBL_IT(MB_EVENT_SEND_DATA), + MB_EVENT_TBL_IT(MB_EVENT_RECV_DATA), + MB_EVENT_TBL_IT(MB_EVENT_RECONNECT), + MB_EVENT_TBL_IT(MB_EVENT_CLOSE), + MB_EVENT_TBL_IT(MB_EVENT_TIMEOUT), +}; + +static esp_err_t mbm_drv_unregister_handlers(void *ctx); + +// The function to print event +const char *driver_event_to_name_r(mb_driver_event_t event) +{ + msg_buffer[0] = 0; + size_t i; + for (i = 0; i < sizeof(event_msg_table) / sizeof(event_msg_table[0]); ++i) { + if (event_msg_table[i].event & event) { + strlcat(msg_buffer, "|", sizeof(msg_buffer)); + strlcat(msg_buffer, event_msg_table[i].msg, sizeof(msg_buffer)); + } + } + return msg_buffer; +} + +static esp_err_t init_event_fd(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + if (!mbm_drv_loop_inst_counter) { + esp_vfs_eventfd_config_t config = MB_EVENTFD_CONFIG(); + esp_err_t err = esp_vfs_eventfd_register(&config); + if ((err != ESP_OK) && (err != ESP_ERR_INVALID_STATE)) { + ESP_LOGE(TAG, "eventfd registration fail."); + } + } + pdrv_ctx->event_fd = eventfd(0, 0); + MB_RETURN_ON_FALSE((pdrv_ctx->event_fd > 0), ESP_ERR_INVALID_STATE, TAG, "eventfd init error."); + return (pdrv_ctx->event_fd > 0) ? ESP_OK : ESP_ERR_INVALID_STATE; +} + +static esp_err_t close_event_fd(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + if (mbm_drv_loop_inst_counter) { + close(pdrv_ctx->event_fd); + } else { + ESP_LOGW(TAG, "close eventfd (%d).", (int)pdrv_ctx->event_fd); + return esp_vfs_eventfd_unregister(); + } + return ESP_OK; +} + +int32_t write_event(void *ctx, mb_event_info_t *pevent) +{ + MB_RETURN_ON_FALSE((pevent && ctx), -1, TAG, "wrong arguments."); + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t err = esp_event_post_to(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), (int32_t)pevent->event_id, pevent, + sizeof(mb_event_info_t), MB_EVENT_TOUT); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "%p, event loop send fail, err = %d.", ctx, (int)err); + return -1; + } + int32_t ret = write(pdrv_ctx->event_fd, (char *)&pevent->val, sizeof(mb_event_info_t)); + return (ret == sizeof(mb_event_info_t)) ? pevent->event_id : -1; +} + +static int32_t read_event(void *ctx, mb_event_info_t *pevent) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + MB_RETURN_ON_FALSE(pevent, ESP_ERR_INVALID_STATE, TAG, "cannot get event."); + int ret = read(pdrv_ctx->event_fd, (char *)&pevent->val, sizeof(mb_event_info_t)); + return (ret == sizeof(mb_event_info_t)) ? pevent->event_id : -1; +} + +static esp_err_t mbm_drv_event_loop_init(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t err = ESP_OK; + /* Create Event loop without task (will be created separately)*/ + esp_event_loop_args_t loop_args = { + .queue_size = MB_EVENT_QUEUE_SZ, + .task_name = NULL + }; + if (!mbm_drv_loop_handle && !mbm_drv_loop_inst_counter) { + err = esp_event_loop_create(&loop_args, &mbm_drv_loop_handle); + MB_RETURN_ON_FALSE(((err == ESP_OK) && mbm_drv_loop_handle), ESP_ERR_INVALID_STATE, + TAG, "create event loop failed, err=%d.", (int)err); + } + pdrv_ctx->event_loop_hdl = mbm_drv_loop_handle; + if (asprintf(&pdrv_ctx->loop_name, "loop:%p", ctx) == -1) { + abort(); + } + return err; +} + +static esp_err_t mbm_drv_event_loop_deinit(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t err = ESP_OK; + // delete event loop */ + if (mbm_drv_loop_handle && mbm_drv_loop_inst_counter) { + ESP_LOGW(TAG, "delete loop inst: %s.", pdrv_ctx->loop_name); + mbm_drv_loop_inst_counter--; + } + if (mbm_drv_loop_handle && !mbm_drv_loop_inst_counter) { + err = esp_event_loop_delete(mbm_drv_loop_handle); + ESP_LOGW(TAG, "delete event loop: %p.", mbm_drv_loop_handle); + mbm_drv_loop_handle = NULL; + free(pdrv_ctx->loop_name); + pdrv_ctx->loop_name = NULL; + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, "delete event loop failed, error=%d.", (int)err); + } + return err; +} + +static esp_err_t mbm_drv_register_handlers(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t ret = ESP_ERR_INVALID_STATE; + + ret = esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_READY, + &on_ready, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_OPEN, + &on_open, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_RESOLVE, + &on_resolve, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_CONNECT, + &on_connect, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_SEND_DATA, + &on_send_data, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_RECV_DATA, + &on_recv_data, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_RECONNECT, + &on_reconnect, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_CLOSE, + &on_close, ctx, &pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_register_with(mbm_drv_loop_handle, MB_EVENT_BASE(ctx), MB_EVENT_TIMEOUT, + &on_timeout, ctx, &pdrv_ctx->event_handler); + MB_RETURN_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE , + TAG, "%p, event handler %p, registration error.", pdrv_ctx, pdrv_ctx->event_handler); + + return ESP_OK; +} + +static esp_err_t mbm_drv_unregister_handlers(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t ret = ESP_ERR_INVALID_STATE; + ESP_LOGW(TAG, "%p, event handler %p, unregister.", pdrv_ctx, pdrv_ctx->event_handler); + + ret = esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_READY, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_OPEN, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_RESOLVE, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_CONNECT, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_SEND_DATA, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_RECV_DATA, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_RECONNECT, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_CLOSE, pdrv_ctx->event_handler); + ret |= esp_event_handler_instance_unregister_with(mbm_drv_loop_handle, + MB_EVENT_BASE(ctx), MB_EVENT_TIMEOUT, pdrv_ctx->event_handler); + MB_RETURN_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE , + TAG, "%p, event handler %p, unregister error.", pdrv_ctx, pdrv_ctx->event_handler); + + return ESP_OK; +} + +static esp_err_t init_queues(mb_slave_info_t *mb_slave) +{ + mb_slave->rx_queue = queue_create(MB_RX_QUEUE_MAX_SIZE); + MB_RETURN_ON_FALSE(mb_slave->rx_queue, ESP_ERR_NO_MEM, TAG, "create rx queue failed"); + mb_slave->tx_queue = queue_create(MB_TX_QUEUE_MAX_SIZE); + MB_RETURN_ON_FALSE(mb_slave->tx_queue, ESP_ERR_NO_MEM, TAG, "create tx queue failed"); + return ESP_OK; +} + +static void delete_queues(mb_slave_info_t *pmb_slave) +{ + queue_delete(pmb_slave->rx_queue); + queue_delete(pmb_slave->tx_queue); + pmb_slave->rx_queue = NULL; + pmb_slave->tx_queue = NULL; +} + +static inline void mbm_drv_lock(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + CRITICAL_SECTION_LOCK(pdrv_ctx->lock); +} + +static inline void mbm_drv_unlock(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + CRITICAL_SECTION_UNLOCK(pdrv_ctx->lock); +} + +__attribute__((unused)) +static mb_sock_state_t mbm_drv_get_slave_state(void *ctx, int fd) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave = pdrv_ctx->mb_slave_info[fd]; + return (pslave) ? atomic_load(&pslave->addr_info.state) : MB_SOCK_STATE_UNDEF; +} + +static void mbm_drv_check_suspend_shutdown(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + + if (pdrv_ctx->close_done_sema) { + mb_status_flags_t status = mbm_drv_wait_status_flag(ctx, (MB_FLAG_SHUTDOWN | MB_FLAG_SUSPEND), 0); + ESP_LOGW(TAG, "%p, driver check shutdown (%d)...", ctx, (int)status); + if (status & MB_FLAG_SHUTDOWN) { + xSemaphoreGive(pdrv_ctx->close_done_sema); + ESP_LOGW(TAG, "%p, driver task shutdown...", ctx); + vTaskDelete(NULL); + } else if (status & MB_FLAG_SUSPEND) { + xSemaphoreGive(pdrv_ctx->close_done_sema); + ESP_LOGW(TAG, "%p, driver task is suspended...", ctx); + vTaskSuspend(NULL); + } + } +} + +static mb_status_flags_t mbm_drv_set_status_flag(void *ctx, mb_status_flags_t mask) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + return (mb_status_flags_t)xEventGroupSetBits(pdrv_ctx->status_flags_hdl, (EventBits_t)mask); +} + +static mb_status_flags_t mbm_drv_clear_status_flag(void *ctx, mb_status_flags_t mask) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + return (mb_status_flags_t)xEventGroupClearBits(pdrv_ctx->status_flags_hdl, (EventBits_t)mask); +} + +mb_status_flags_t mbm_drv_wait_status_flag(void *ctx, mb_status_flags_t mask, uint32_t tout_ms) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + return (mb_status_flags_t)xEventGroupWaitBits(pdrv_ctx->status_flags_hdl, + (BaseType_t)(mask), + pdFALSE, + pdFALSE, + pdMS_TO_TICKS(tout_ms)); +} + +int mbm_drv_open(void *ctx, mb_uid_info_t addr_info, int flags) +{ + int fd = -1; + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave_info = NULL; + // Find free fd and initialize + for (fd = 0; fd < MB_MAX_FDS; fd++) { + pslave_info = pdrv_ctx->mb_slave_info[fd]; + if (!pslave_info) { + pslave_info = calloc(1, sizeof(mb_slave_info_t)); + if (!pslave_info) { + goto err; + } + ESP_LOGW(TAG, "%p, open vfd: %d, sl_addr: %02x, node: %s:%u", + ctx, fd, (int8_t)addr_info.uid, + addr_info.ip_addr_str, (unsigned)addr_info.port); + if (init_queues(pslave_info) != ESP_OK) { + goto err; + } + if (pdrv_ctx->mb_slave_open_count > MB_MAX_FDS) { + goto err; + } + mbm_drv_lock(ctx); + pdrv_ctx->mb_slave_open_count++; + pslave_info->index = fd; + pslave_info->fd = fd; + pslave_info->sock_id = -1; + pslave_info->error = -1; + pslave_info->recv_err = -1; + pslave_info->addr_info = addr_info; + pslave_info->addr_info.ip_addr_str = NULL; + pslave_info->addr_info.index = fd; + pslave_info->send_time = esp_timer_get_time(); + pslave_info->recv_time = esp_timer_get_time(); + pslave_info->tid_counter = 0; + pslave_info->send_counter = 0; + pslave_info->recv_counter = 0; + pslave_info->is_blocking = ((flags & O_NONBLOCK) == 0); + pdrv_ctx->mb_slave_info[fd] = pslave_info; + // mark opened slave in the open set + FD_SET(fd, &pdrv_ctx->open_set); + mbm_drv_unlock(ctx); + MB_SET_SLAVE_STATE(pslave_info, MB_SOCK_STATE_OPENED); + DRIVER_SEND_EVENT(ctx, MB_EVENT_OPEN, fd); + return fd; + } + } +err: + free(pslave_info); + pdrv_ctx->mb_slave_info[fd] = NULL; + mbm_drv_unlock(ctx); + return INVALID_FD; +} + +// writes data into tx queue +ssize_t mbm_drv_write(void *ctx, int fd, const void *data, size_t size) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + ssize_t ret = -1; + + if (size == 0) { + return 0; + } + + mb_slave_info_t *pslave_info = pdrv_ctx->mb_slave_info[fd]; + if (!pslave_info) { + errno = EBADF; + return 0; + } + + if (MB_GET_SLAVE_STATE(pslave_info) >= MB_SOCK_STATE_CONNECTED) { + if (queue_push(pslave_info->tx_queue, (void *)data, size, NULL) == ESP_OK) { + ret = size; + mbm_drv_lock(ctx); + pdrv_ctx->mb_slave_curr_info = pslave_info; + pdrv_ctx->curr_slave_index = pslave_info->index; + mbm_drv_unlock(ctx); + // Inform FSM that is new frame data is ready to be send + DRIVER_SEND_EVENT(ctx, MB_EVENT_SEND_DATA, pslave_info->index); + } else { + // I/O error + errno = EIO; + } + } else { + // bad file desc + errno = EBADF; + } + return ret; +} + +// reads data from rx queue +ssize_t mbm_drv_read(void *ctx, int fd, void *data, size_t size) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave_info = pdrv_ctx->mb_slave_info[fd]; + if (!pslave_info) { + errno = EBADF; + return 0; + } + + // fd might be in process of closing (close was already called but preempted) + if (MB_GET_SLAVE_STATE(pslave_info) < MB_SOCK_STATE_CONNECTED) { + // bad file desc + errno = EBADF; + return -1; + } + + if (size == 0) { + return 0; + } + + ssize_t actual_size = -1; + if ((actual_size = queue_pop(pslave_info->rx_queue, data, size, NULL)) < 0) { + errno = EAGAIN; + } + + return actual_size; +} + +int mbm_drv_close(void *ctx, int fd) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave_info = pdrv_ctx->mb_slave_info[fd]; // get address of configuration + + if (!pslave_info) { + // not valid opened fd + errno = EBADF; + return -1; + } + + // stop socket + MB_SET_SLAVE_STATE(pslave_info, MB_SOCK_STATE_CLOSED); + // port_close_connection((mb_slave_info_t *)pslave_info); + mbm_drv_lock(ctx); + FD_CLR(fd, &pdrv_ctx->open_set); + delete_queues(pslave_info); + if (pslave_info->addr_info.node_name_str != pslave_info->addr_info.ip_addr_str) { + free((void *)pslave_info->addr_info.ip_addr_str); // slave ip addr string shall be freed + } + free((void *)pslave_info->addr_info.node_name_str); + pslave_info->addr_info.node_name_str = NULL; + pslave_info->addr_info.ip_addr_str = NULL; + free(pslave_info); + pdrv_ctx->mb_slave_info[fd] = NULL; + mbm_drv_unlock(ctx); + + return 0; +} + +static mb_slave_info_t *mbm_drv_get_next_config_from_set(void *ctx, int *pfd, fd_set *pfdset) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + if (!pfdset || !pfd) { + return NULL; + } + mb_slave_info_t *pslave_info = NULL; + for (int fd = *pfd; fd < MB_MAX_FDS; fd++) { + pslave_info = pdrv_ctx->mb_slave_info[fd]; + if (pslave_info && (pslave_info->sock_id > 0) + && (MB_GET_SLAVE_STATE(pslave_info) >= MB_SOCK_STATE_CONNECTED) + && (FD_ISSET(pslave_info->index, pfdset) || (FD_ISSET(pslave_info->sock_id, pfdset)))) { + *pfd = fd; + //FD_CLR(pslave_info->sock_id, pfdset); + return pslave_info; + } + } + return NULL; +} + +mb_slave_info_t *mbm_drv_get_slave_info_from_addr(void *ctx, uint8_t slave_addr) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave_info = NULL; + for (int fd = 0; fd < MB_MAX_FDS; fd++) { + pslave_info = pdrv_ctx->mb_slave_info[fd]; + if (pslave_info && pslave_info->addr_info.uid == slave_addr) { + return pslave_info; + } + } + return NULL; +} + +static int mbm_drv_get_socket_max_fd(void *ctx, fd_set *pfdset) +{ + mb_slave_info_t *pslave_info = NULL; + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + // Setup select waiting for eventfd && socket events + FD_ZERO(pfdset); + int max_fd = -1; + for (int i = 0; i < MB_MAX_FDS; i++) { + pslave_info = pdrv_ctx->mb_slave_info[i]; + if (pslave_info && MB_GET_SLAVE_STATE(pslave_info) >= MB_SOCK_STATE_CONNECTED) { + FD_SET(pslave_info->sock_id, pfdset); + max_fd = pslave_info->sock_id > max_fd ? pslave_info->sock_id : max_fd; + } + } + max_fd = (pdrv_ctx->event_fd > max_fd) ? pdrv_ctx->event_fd : max_fd; + FD_SET(pdrv_ctx->event_fd, pfdset); + return max_fd; +} + +// Wait socket ready event during timeout +static int mbm_drv_wait_fd_events(void *ctx, fd_set *pfdset, fd_set *perrset, int time_ms) +{ + fd_set readset = *pfdset; + int ret = 0; + struct timeval tv; + + if (!ctx || !pfdset) { + return -1; + } + + tv.tv_sec = time_ms / 1000; + tv.tv_usec = (time_ms - (tv.tv_sec * 1000)) * 1000; + + // fill the readset according to the active fds + int max_fd = mbm_drv_get_socket_max_fd(ctx, &readset); + if (perrset) { + *perrset = readset; // initialize error set if used + } + + ret = select(max_fd + 1, &readset, NULL, perrset, &tv); + if (ret == 0) { + // No respond from slave during timeout + ret = ERR_TIMEOUT; + } else if (ret < 0) { + ret = -1; + } + *pfdset = readset; + return ret; +} + +esp_err_t mbm_drv_start_task(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + (void)mbm_drv_clear_status_flag(ctx, MB_FLAG_SUSPEND); + ESP_LOGW(TAG, "%p, resume tcp driver task.", ctx); + vTaskResume(pdrv_ctx->mb_tcp_task_handle); + return ESP_OK; +} + +esp_err_t mbm_drv_stop_task(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + esp_err_t err = ESP_ERR_TIMEOUT; + if (!pdrv_ctx->close_done_sema) { + pdrv_ctx->close_done_sema = xSemaphoreCreateBinary(); + } + (void)mbm_drv_set_status_flag(ctx, MB_FLAG_SUSPEND); + // Check if we can safely suspend the port task (workaround for issue with deadlock in suspend) + if (!pdrv_ctx->close_done_sema + || !(mbm_drv_wait_status_flag(ctx, MB_FLAG_SUSPEND, 1) & MB_FLAG_SUSPEND) + || (xSemaphoreTake(pdrv_ctx->close_done_sema, pdMS_TO_TICKS(MB_WAIT_DONE_MS)) != pdTRUE) + ) { + ESP_LOGW(TAG, "%p, could not stop driver task during timeout.", ctx); + vTaskSuspend(pdrv_ctx->mb_tcp_task_handle); + err = ESP_OK; + } + ESP_LOGW(TAG, "%p, stop tcp driver task.", ctx); + if (pdrv_ctx->close_done_sema) { + vSemaphoreDelete(pdrv_ctx->close_done_sema); + pdrv_ctx->close_done_sema = NULL; + } + return err; +} + +void mbm_drv_tcp_task(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + ESP_LOGD(TAG, "Start of driver task."); + while (1) { + fd_set readset, errorset; + // check all active socket and fd events + int ret = mbm_drv_wait_fd_events(ctx, &readset, &errorset, MB_SELECT_WAIT_MS); + if (ret == ERR_TIMEOUT) { + // timeout occured waiting for the vfds + ESP_LOGW(TAG, "%p, task select timeout.", ctx); + mbm_drv_check_suspend_shutdown(ctx); + } else if (ret == -1) { + // error occured during waiting for vfds activation + ESP_LOGW(TAG, "%p, task select error.", ctx); + mbm_drv_check_suspend_shutdown(ctx); + ESP_LOGD(TAG, "%p, socket error, fdset: %" PRIx64, ctx, *(uint64_t *)&errorset); + } else { + // Is the fd event triggered, process the event + if (FD_ISSET(pdrv_ctx->event_fd, &readset)) { + mb_event_info_t mbm_event = {0}; + int32_t event_id = read_event(ctx, &mbm_event); + ESP_LOGW(TAG, "%p, fd event get: 0x%02x:%d, %s", + ctx, (int)event_id, (int)mbm_event.opt_fd, driver_event_to_name_r(event_id)); + mbm_drv_check_suspend_shutdown(ctx); + // Drive the event loop + esp_err_t err = esp_event_loop_run(mbm_drv_loop_handle, pdMS_TO_TICKS(MB_TCP_EVENT_LOOP_TICK_MS)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%p, event loop run, returns fail: %x", ctx, (int)err); + } + } else { // socket data is ready, process each socket event + mbm_drv_check_suspend_shutdown(ctx); + int curr_fd = 0; + mb_slave_info_t *pslave_info = NULL; + ESP_LOGW(TAG, "%p, socket event active: %" PRIx64, ctx, *(uint64_t *)&readset); + while(((pslave_info = (mb_slave_info_t *)mbm_drv_get_next_config_from_set(ctx, &curr_fd, &readset)) + && (curr_fd < MB_MAX_FDS))) { + if (FD_ISSET(pslave_info->sock_id, &pdrv_ctx->conn_set)) { + // The data is ready in the socket, read frame and queue + FD_CLR(pslave_info->sock_id, &readset); + int ret = port_read_packet(pdrv_ctx->parent, pslave_info); + if (ret > 0) { + ESP_LOGD(TAG, "%p, "MB_SLAVE_FMT(", frame received."), ctx, (int)pslave_info->fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + mbm_drv_lock(ctx); + pslave_info->recv_time = esp_timer_get_time(); + mbm_drv_unlock(ctx); + DRIVER_SEND_EVENT(ctx, MB_EVENT_RECV_DATA, pslave_info->index); + } else if (ret == ERR_TIMEOUT) { + ESP_LOGD(TAG, "%p, "MB_SLAVE_FMT(", frame read timeout."), ctx, (int)pslave_info->fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + } else if (ret == ERR_BUF) { + // After retries a response with incorrect TID received, process failure. + pdrv_ctx->event_cbs.mb_sync_event_cb(pdrv_ctx->event_cbs.port_arg, MB_SYNC_EVENT_RECV_FAIL); + ESP_LOGW(TAG, "%p, "MB_SLAVE_FMT(", frame error."), ctx, (int)pslave_info->fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + } else { + ESP_LOGE(TAG, "%p, "MB_SLAVE_FMT(", critical error=%d, errno=%u."), ctx, (int)pslave_info->fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str, (int)ret, (unsigned)errno); + if (ret == ERR_CONN) { + ESP_LOGW(TAG, "%p, "MB_SLAVE_FMT(", connection lost."), ctx, (int)pslave_info->fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + DRIVER_SEND_EVENT(ctx, MB_EVENT_RECONNECT, pslave_info->index); + } + } + } + curr_fd++; + mbm_drv_check_suspend_shutdown(ctx); + } + } + } + } +} + +EVENT_HANDLER(on_reconnect) +{ + static int curr_fd = 0; + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + mb_slave_info_t *pslave_info = NULL; + if (MB_CHECK_FD_RANGE(pevent_info->opt_fd)) { + curr_fd = pevent_info->opt_fd; + pslave_info = mbm_drv_get_next_config_from_set(ctx, &curr_fd, &pdrv_ctx->conn_set); + if (pslave_info) { + uint64_t last_read_div_us = esp_timer_get_time() - pslave_info->recv_time; + ESP_LOGW(TAG, "%p, slave: %d, sock: %d, IP:%s, check connection, time = %" PRId64 ", rcv_time: %" PRId64, + ctx, (int)pslave_info->index, (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str, + (esp_timer_get_time() / 1000), pslave_info->recv_time / 1000); + if (last_read_div_us >= (uint64_t)(MB_RECONNECT_TIME_MS * 1000)) { + err_t err = port_check_alive(pslave_info, MB_RECONNECT_TIME_MS); + if (err < 0) { + ESP_LOGW(TAG, "%p, slave: %d, sock: %d, inactive for %" PRId64 " [ms], reconnect...", + ctx, (int)pslave_info->index, (int)pslave_info->sock_id, + (last_read_div_us / 1000)); + MB_SET_SLAVE_STATE(pslave_info, MB_SOCK_STATE_OPENED); + FD_CLR(pslave_info->sock_id, &pdrv_ctx->conn_set); + port_close_connection(pslave_info); + mbm_drv_lock(ctx); + pdrv_ctx->slave_conn_count--; + mbm_drv_unlock(ctx); + DRIVER_SEND_EVENT(ctx, MB_EVENT_CONNECT, pslave_info->index); + } else { + curr_fd++; + } + } else { + ESP_LOGW(TAG, "%p, slave: %d, sock: %d, inactive for %" PRId64 " [ms], wait reconnection...", + ctx, (int)pslave_info->index, (int)pslave_info->sock_id, + (last_read_div_us / 1000)); + } + } + } else if (pevent_info->opt_fd < 0) { + // send resolve event to all slaves + for (int fd = 0; fd < pdrv_ctx->mb_slave_open_count; fd++) { + mb_slave_info_t *pslave = pdrv_ctx->mb_slave_info[fd]; + if (pslave && (MB_GET_SLAVE_STATE(pslave) == MB_SOCK_STATE_OPENED) + && FD_ISSET(pslave->index, &pdrv_ctx->open_set)) { + DRIVER_SEND_EVENT(ctx, MB_EVENT_RESOLVE, pslave->index); + } + mbm_drv_check_suspend_shutdown(ctx); + } + } +} + +EVENT_HANDLER(on_ready) +{ + // The driver is registered + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); +} + +EVENT_HANDLER(on_open) +{ + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); +} + +EVENT_HANDLER(on_resolve) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + if (MB_CHECK_FD_RANGE(pevent_info->opt_fd)) { + // The mdns is not used in the main app, then can use manually defined IPs + int fd = pevent_info->opt_fd; + mb_slave_info_t *pslave = pdrv_ctx->mb_slave_info[fd]; + if (pslave && (MB_GET_SLAVE_STATE(pslave) == MB_SOCK_STATE_OPENED) + && FD_ISSET(pslave->index, &pdrv_ctx->open_set)) { + // The slave IP is defined manually + if (port_check_host_addr(pslave->addr_info.node_name_str, NULL)) { + pslave->addr_info.ip_addr_str = pslave->addr_info.node_name_str; + ESP_LOGW(TAG, "%p, slave: %d, IP address [%s], added to connection list.", ctx, (int)fd, pslave->addr_info.ip_addr_str); + MB_SET_SLAVE_STATE(pslave, MB_SOCK_STATE_RESOLVED); + DRIVER_SEND_EVENT(ctx, MB_EVENT_CONNECT, pslave->index); + } else { +#ifdef MB_MDNS_IS_INCLUDED + int ret = port_resolve_mdns_host(pslave->addr_info.node_name_str, (char **)&pslave->addr_info.ip_addr_str); + if (ret > 0) { + ESP_LOGI(TAG, "%p, slave: %d, resolved with IP:%s.", ctx, (int)fd, pslave->addr_info.ip_addr_str); + MB_SET_SLAVE_STATE(pslave, MB_SOCK_STATE_RESOLVED); + DRIVER_SEND_EVENT(ctx, MB_EVENT_CONNECT, pslave->index); + } else { + // continue resolve while not resolved + DRIVER_SEND_EVENT(ctx, MB_EVENT_RESOLVE, pslave->index); + } +#else + ESP_LOGE(TAG, "%p, slave: %d, IP:%s, mdns service is not supported.", ctx, (int)fd, pslave->addr_info.node_name_str); + DRIVER_SEND_EVENT(ctx, MB_EVENT_RESOLVE, pslave->index); +#endif + } + } + } else if (pevent_info->opt_fd < 0) { + // Todo: Removed from this version + // #ifdef MB_MDNS_IS_INCLUDED + // // If the mDNS feature support is enabled, use it to resolve the slave IP + // res = mbm_drv_resolve_mdns_service(ctx, "_modbus", "_tcp", pdrv_ctx->addr_type); + // ESP_LOGW(TAG, "%p, use mdns to resolve slave: %d, resolved: %d devices.", ctx, (int)pevent_info->opt_fd, res); + // #else + for (int fd = 0; fd < pdrv_ctx->mb_slave_open_count; fd++) { + mb_slave_info_t *pslave = pdrv_ctx->mb_slave_info[fd]; + if (pslave && (MB_GET_SLAVE_STATE(pslave) == MB_SOCK_STATE_OPENED) + && FD_ISSET(pslave->index, &pdrv_ctx->open_set)) { + DRIVER_SEND_EVENT(ctx, MB_EVENT_RESOLVE, pslave->index); + } + mbm_drv_check_suspend_shutdown(ctx); + } + // #endif + } +} + +EVENT_HANDLER(on_connect) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_slave_info_t *pslave_info = NULL; + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + err_t err = ERR_CONN; + if (MB_CHECK_FD_RANGE(pevent_info->opt_fd)) { + pslave_info = pdrv_ctx->mb_slave_info[pevent_info->opt_fd]; + if (pslave_info && (MB_GET_SLAVE_STATE(pslave_info) < MB_SOCK_STATE_CONNECTED)) { + ESP_LOGW(TAG, "%p, connection phase, slave: #%d(%d) [%s].", + ctx, (int)pevent_info->opt_fd, (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + if (pslave_info->sock_id != -1) { + port_close_connection(pslave_info); + } + err = port_connect(ctx, pslave_info); + switch (err) { + case ERR_OK: + if (!FD_ISSET(pslave_info->sock_id, &pdrv_ctx->conn_set)) { + FD_SET(pslave_info->sock_id, &pdrv_ctx->conn_set); + mbm_drv_lock(ctx); + pdrv_ctx->slave_conn_count++; + pdrv_ctx->max_conn_sd = (pslave_info->sock_id > pdrv_ctx->max_conn_sd) ? (int)pslave_info->sock_id : pdrv_ctx->max_conn_sd; + // Update time stamp for connected slaves + pslave_info->send_time = esp_timer_get_time(); + pslave_info->recv_time = esp_timer_get_time(); + mbm_drv_unlock(ctx); + ESP_LOGI(TAG, "%p, slave: #%d, sock:%d, IP: %s, is connected.", + ctx, (int)pevent_info->opt_fd, (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + } + MB_SET_SLAVE_STATE(pslave_info, MB_SOCK_STATE_CONNECTED); + port_keep_alive(pslave_info); + break; + case ERR_INPROGRESS: + if (FD_ISSET(pslave_info->sock_id, &pdrv_ctx->conn_set)) { + FD_CLR(pslave_info->sock_id, &pdrv_ctx->conn_set); + ESP_LOGW(TAG, "%p, slave: #%d, sock:%d, IP:%s, connect fail error = %d.", + ctx, (int)pevent_info->opt_fd, (int)pslave_info->sock_id, + pslave_info->addr_info.ip_addr_str, (int)err); + mbm_drv_lock(ctx); + if (pdrv_ctx->slave_conn_count) { + pdrv_ctx->slave_conn_count--; + } + mbm_drv_unlock(ctx); + } + MB_SET_SLAVE_STATE(pslave_info, MB_SOCK_STATE_CONNECTING); + vTaskDelay(MB_CONN_TICK_TIMEOUT); + // try to connect to slave and check connection again if it is not connected + DRIVER_SEND_EVENT(ctx, MB_EVENT_CONNECT, pevent_info->opt_fd); + break; + case ERR_CONN: + ESP_LOGE(TAG, "Modbus connection phase, slave: %d [%s], connection error (%d).", + (int)pevent_info->opt_fd, pslave_info->addr_info.ip_addr_str, (int)err); + break; + default: + ESP_LOGE(TAG, "Invalid error state, slave: %d [%s], error = %d.", + (int)pevent_info->opt_fd, pslave_info->addr_info.ip_addr_str, (int)err); + break; + } + } + } else { + // if the event fd is -1 (an event for all slaves), + // then perform connection phase for all resolved slaves sending the connection event + for (int node = 0; (node < MB_TCP_PORT_MAX_CONN); node++) { + pslave_info = pdrv_ctx->mb_slave_info[node]; + if (pslave_info && (MB_GET_SLAVE_STATE(pslave_info) == MB_SOCK_STATE_RESOLVED)) { + if (((pslave_info->sock_id < 0) || !FD_ISSET(pslave_info->sock_id, &pdrv_ctx->conn_set)) + && FD_ISSET(node, &pdrv_ctx->open_set)) { + DRIVER_SEND_EVENT(ctx, MB_EVENT_CONNECT, pslave_info->index); + } + } + mbm_drv_check_suspend_shutdown(ctx); + } + } + ESP_LOGD(TAG, "Opened/connected: %u, %u.", + (unsigned)pdrv_ctx->mb_slave_open_count, (unsigned)pdrv_ctx->slave_conn_count); + if (pdrv_ctx->mb_slave_open_count == pdrv_ctx->slave_conn_count) { + if (pdrv_ctx->event_cbs.on_conn_done_cb) { + pdrv_ctx->event_cbs.on_conn_done_cb(pdrv_ctx->event_cbs.arg); + } + ESP_LOGI(TAG, "%p, Connected: %u, %u, start polling.", + ctx, (unsigned)pdrv_ctx->mb_slave_open_count, (unsigned)pdrv_ctx->slave_conn_count); + } +} + +EVENT_HANDLER(on_send_data) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + mb_slave_info_t *pinfo = pdrv_ctx->mb_slave_info[pevent_info->opt_fd]; + if (pinfo && !queue_is_empty(pinfo->tx_queue)) { + uint8_t tx_buffer[MB_TCP_BUFF_MAX_SIZE] = {0}; + ESP_LOGW(TAG, "%p, get info: %d, sock_id: %d, queue_state: %d, state: %d.", + ctx, (int)pevent_info->opt_fd, (int)pinfo->sock_id, + (int)queue_is_empty(pinfo->tx_queue), (int)MB_GET_SLAVE_STATE(pinfo)); + size_t sz = queue_pop(pinfo->tx_queue, tx_buffer, sizeof(tx_buffer), NULL); + if (MB_GET_SLAVE_STATE(pinfo) < MB_SOCK_STATE_CONNECTED) { + mbm_drv_lock(ctx); + pdrv_ctx->mb_slave_curr_info = pinfo; + mbm_drv_unlock(ctx); + // if slave is not connected, drop data. + ESP_LOGE(TAG, "%p, "MB_SLAVE_FMT(", is invalid, drop send data."), + ctx, (int)pinfo->index, (int)pinfo->sock_id, pinfo->addr_info.ip_addr_str); + return; + } + int ret = port_write_poll(pinfo, tx_buffer, sz, MB_TCP_SEND_TIMEOUT_MS); + if (ret < 0) { + ESP_LOGE(TAG, "%p, "MB_SLAVE_FMT(", send data failure, err(errno) = %d(%u)."), + ctx, (int)pinfo->index, (int)pinfo->sock_id, + pinfo->addr_info.ip_addr_str, (int)ret, (unsigned)errno); + DRIVER_SEND_EVENT(ctx, MB_EVENT_RECONNECT, pinfo->index); + pinfo->error = ret; + } else { + ESP_LOGD(TAG, "%p, "MB_SLAVE_FMT(", send data successful: TID=0x%04x, %d (bytes), errno %d"), + ctx, (int)pinfo->index, (int)pinfo->sock_id, + pinfo->addr_info.ip_addr_str, pinfo->tid_counter, (int)ret, (unsigned)errno); + pinfo->error = 0; + // Every successful write increase TID counter + if (pinfo->tid_counter < (USHRT_MAX - 1)) { + pinfo->tid_counter++; + } else { + pinfo->tid_counter = (uint16_t)(pinfo->index << 8U); + } + } + pdrv_ctx->event_cbs.mb_sync_event_cb(pdrv_ctx->event_cbs.port_arg, MB_SYNC_EVENT_SEND_OK); + mbm_drv_lock(ctx); + pdrv_ctx->mb_slave_curr_info = pinfo; + pinfo->send_time = esp_timer_get_time(); + pinfo->send_counter = (pinfo->send_counter < (USHRT_MAX - 1)) ? (pinfo->send_counter + 1) : 0; + mbm_drv_unlock(ctx); + // Get send buffer from stack + ESP_LOG_BUFFER_HEX_LEVEL("SENT", tx_buffer, sz, ESP_LOG_WARN); + } +} + +EVENT_HANDLER(on_recv_data) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + size_t sz = 0; + uint8_t pbuf[MB_TCP_BUFF_MAX_SIZE] = {0}; + // Get frame from queue, check for correctness, push back correct frame and generate receive condition. + // Removes incorrect or expired frames from the queue, leave just correct one then sent sync event + mb_slave_info_t *pslave_info = pdrv_ctx->mb_slave_info[pevent_info->opt_fd]; + if (pslave_info) { + ESP_LOGI(TAG, "%p, slave #%d(%d) [%s], receive data ready.", ctx, (int)pevent_info->opt_fd, + (int)pslave_info->sock_id, pslave_info->addr_info.ip_addr_str); + while ((sz <= 0) && !queue_is_empty(pslave_info->rx_queue)) { + size_t sz = queue_pop(pslave_info->rx_queue, pbuf, MB_TCP_BUFF_MAX_SIZE, NULL); + if ((sz > MB_TCP_FUNC) && (sz < sizeof(pbuf))) { + uint16_t tid = MB_TCP_MBAP_GET_FIELD(pbuf, MB_TCP_TID); + ESP_LOGW(TAG, "%p, packet TID: #%.4x received.", ctx, tid); + if (tid == (pslave_info->tid_counter - 1)) { + queue_push(pslave_info->rx_queue, pbuf, sz, NULL); + mbm_drv_lock(ctx); + pslave_info->recv_time = esp_timer_get_time(); + mbm_drv_unlock(ctx); + // send receive event to modbus object + pdrv_ctx->event_cbs.mb_sync_event_cb(pdrv_ctx->event_cbs.port_arg, MB_SYNC_EVENT_RECV_OK); + break; + } + } + mbm_drv_check_suspend_shutdown(ctx); + } + } +} + +EVENT_HANDLER(on_close) +{ + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s, fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + // if close all sockets event is received + if (pevent_info->opt_fd < 0) { + (void)mbm_drv_clear_status_flag(pdrv_ctx, MB_FLAG_DISCONNECTED); + for (int fd = 0; fd < MB_MAX_FDS; fd++) { + mb_slave_info_t *pslave = pdrv_ctx->mb_slave_info[fd]; + if (pslave && (MB_GET_SLAVE_STATE(pslave) >= MB_SOCK_STATE_OPENED) + && FD_ISSET(pslave->index, &pdrv_ctx->open_set)) { + mbm_drv_lock(ctx); + // Check connection and unregister slave + if ((pslave->sock_id > 0) && (FD_ISSET(pslave->sock_id, &pdrv_ctx->conn_set)) ) { + FD_CLR(pslave->sock_id, &pdrv_ctx->conn_set); + if (pdrv_ctx->slave_conn_count) { + pdrv_ctx->slave_conn_count--; + } + } + FD_CLR(pslave->index, &pdrv_ctx->open_set); + mbm_drv_unlock(ctx); + // close the socket connection, if active + (void)port_close_connection(pslave); + // change slave state immediately to release from select + MB_SET_SLAVE_STATE(pslave, MB_SOCK_STATE_READY); + } + } + (void)mbm_drv_set_status_flag(pdrv_ctx, MB_FLAG_DISCONNECTED); + mbm_drv_check_suspend_shutdown(ctx); + } +} + +EVENT_HANDLER(on_timeout) +{ + // Slave timeout triggered + mb_event_info_t *pevent_info = (mb_event_info_t *)data; + ESP_LOGW(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)pevent_info->opt_fd); + mbm_drv_check_suspend_shutdown(ctx); +} + +esp_err_t mbm_drv_register(port_driver_t **ctx) +{ + port_driver_t driver_config = MB_DRIVER_CONFIG_DEFAULT; + esp_err_t ret = ESP_ERR_INVALID_STATE; + + port_driver_t *pctx = (port_driver_t *)calloc(1, sizeof(port_driver_t)); + MB_GOTO_ON_FALSE((pctx), ESP_ERR_NO_MEM, error, TAG, "%p, driver allocation fail.", pctx); + *pctx = driver_config; + //spinlock_initialize(&driver_config.spin_lock); + CRITICAL_SECTION_INIT(pctx->lock); + + // create and initialize modbus driver conetext structure + pctx->mb_slave_info = calloc(MB_MAX_FDS, sizeof(mb_slave_info_t *)); + MB_GOTO_ON_FALSE((pctx->mb_slave_info), ESP_ERR_NO_MEM, error, TAG, "%p, node allocation fail.", pctx); + + for (int i = 0; i < MB_MAX_FDS; i++) { + pctx->mb_slave_info[i] = NULL; + } + + ret = init_event_fd((void *)pctx); + MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE , error, + TAG, "%p, vfs eventfd init error.", pctx); + + ret = mbm_drv_event_loop_init((void *)pctx); + MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE , error, + TAG, "%p, event loop init error.", pctx); + + ret = mbm_drv_register_handlers((void *)pctx); + MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE , error, + TAG, "%p, event handler registration error.", pctx); + + pctx->status_flags_hdl = xEventGroupCreate(); + MB_GOTO_ON_FALSE((pctx->status_flags_hdl), ESP_ERR_INVALID_STATE, error, + TAG, "%p, mb event group error.", pctx); + + mbm_drv_loop_inst_counter++; + +#ifdef MB_MDNS_IS_INCLUDED + port_start_mdns_service(); +#endif + + // Create task for packet processing + BaseType_t state = xTaskCreatePinnedToCore(mbm_drv_tcp_task, + "mbm_drv_tcp_task", + MB_TASK_STACK_SZ, + pctx, + MB_TASK_PRIO, + &pctx->mb_tcp_task_handle, + MB_PORT_TASK_AFFINITY); + MB_GOTO_ON_FALSE((state == pdTRUE), ESP_ERR_INVALID_STATE , error, + TAG, "%p, event task creation error.", pctx); + + (void)mbm_drv_stop_task(pctx); + + *ctx = pctx; + pctx->is_registered = true; + FD_ZERO(&pctx->open_set); + FD_ZERO(&pctx->conn_set); + DRIVER_SEND_EVENT((void *)pctx, MB_EVENT_READY, -1); + return ESP_OK; + +error: + if (pctx) { + if (pctx->mb_tcp_task_handle) { + vTaskDelete(pctx->mb_tcp_task_handle); + } + if (pctx->event_handler) { + mbm_drv_unregister_handlers(pctx); + pctx->event_handler = NULL; + } + if (mbm_drv_loop_handle) { + (void)esp_event_loop_delete(mbm_drv_loop_handle); + mbm_drv_loop_handle = NULL; + free(pctx->loop_name); + pctx->loop_name = NULL; + } + if (pctx->event_fd) { + close(pctx->event_fd); + (void)esp_vfs_eventfd_unregister(); + } + if (pctx->close_done_sema) { + vSemaphoreDelete(pctx->close_done_sema); + pctx->close_done_sema = NULL; + } + free(pctx->mb_slave_info); + } + free(pctx); + return ret; +} + +esp_err_t mbm_drv_unregister(void *ctx) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + ESP_LOGW(TAG, "%p, driver unregister.", pdrv_ctx); + pdrv_ctx->close_done_sema = xSemaphoreCreateBinary(); + (void)mbm_drv_set_status_flag(ctx, MB_FLAG_SHUTDOWN); + + // Change the state of all slaves to close + //DRIVER_SEND_EVENT(ctx, MB_EVENT_CLOSE, -1); + //(void)mbm_drv_wait_status_flag(ctx, MB_FLAG_DISCONNECTED, MB_RECONNECT_TIME_MS); + + // if no semaphore (alloc issues) or couldn't acquire it, just delete the task + if (!pdrv_ctx->close_done_sema + || !(mbm_drv_wait_status_flag(ctx, MB_FLAG_SHUTDOWN, 0) & MB_FLAG_SHUTDOWN) + || (xSemaphoreTake(pdrv_ctx->close_done_sema, pdMS_TO_TICKS(MB_WAIT_DONE_MS)) != pdTRUE) + ) { + ESP_LOGW(TAG, "%p, driver tasks couldn't exit within timeout -> abruptly deleting the task.", pdrv_ctx); + vTaskDelete(pdrv_ctx->mb_tcp_task_handle); + } + + mbm_drv_unregister_handlers(ctx); + mbm_drv_event_loop_deinit(ctx); + + if (pdrv_ctx->close_done_sema) { + vSemaphoreDelete(pdrv_ctx->close_done_sema); + pdrv_ctx->close_done_sema = NULL; + } + + esp_err_t err = close_event_fd(ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "could not close the eventfd handle, err = %d. Already closed?", err); + } + + for (int i = 0; i < MB_MAX_FDS; i++) { + mb_slave_info_t *pslave_info = pdrv_ctx->mb_slave_info[i]; + if (pslave_info) { + ESP_LOGW(TAG, "%p, close slave instance #%d(%s).", ctx, i, pslave_info->addr_info.node_name_str); + mbm_drv_close(ctx, i); + } + } + + free(pdrv_ctx->mb_slave_info); // free the slave info address array + pdrv_ctx->mb_slave_info = NULL; + + vEventGroupDelete(pdrv_ctx->status_flags_hdl); + + // if the MDNS resolving is enabled, then free it +#ifdef MB_MDNS_IS_INCLUDED + mdns_free(); +#endif + + pdrv_ctx->is_registered = false; + free(pdrv_ctx); + + return ESP_OK; +} + +void mbm_drv_set_cb(void *ctx, void *conn_cb, void *arg) +{ + port_driver_t *pdrv_ctx = GET_CONFIG_PTR(ctx); + mbm_drv_lock(ctx); + pdrv_ctx->event_cbs.on_conn_done_cb = conn_cb; + pdrv_ctx->event_cbs.arg = arg; + mbm_drv_unlock(ctx); +} + +#endif \ No newline at end of file diff --git a/modbus/mb_ports/tcp/port_tcp_driver.h b/modbus/mb_ports/tcp/port_tcp_driver.h new file mode 100644 index 0000000..1976081 --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_driver.h @@ -0,0 +1,296 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +//#include +#include + +#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 diff --git a/modbus/mb_ports/tcp/port_tcp_master.c b/modbus/mb_ports/tcp/port_tcp_master.c new file mode 100644 index 0000000..a9a6a8e --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_master.c @@ -0,0 +1,259 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#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 diff --git a/modbus/mb_ports/tcp/port_tcp_master.h b/modbus/mb_ports/tcp/port_tcp_master.h new file mode 100644 index 0000000..09ba6f5 --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_master.h @@ -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 diff --git a/modbus/mb_ports/tcp/port_tcp_slave.c b/modbus/mb_ports/tcp/port_tcp_slave.c new file mode 100644 index 0000000..21dabbb --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_slave.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#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 \ No newline at end of file diff --git a/modbus/mb_ports/tcp/port_tcp_utils.c b/modbus/mb_ports/tcp/port_tcp_utils.c new file mode 100644 index 0000000..eb055b3 --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_utils.c @@ -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 diff --git a/modbus/mb_ports/tcp/port_tcp_utils.h b/modbus/mb_ports/tcp/port_tcp_utils.h new file mode 100644 index 0000000..5e7160c --- /dev/null +++ b/modbus/mb_ports/tcp/port_tcp_utils.h @@ -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 +#include + +#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 \ No newline at end of file diff --git a/modbus/mb_transports/ascii/ascii_lrc.c b/modbus/mb_transports/ascii/ascii_lrc.c new file mode 100644 index 0000000..078ef4c --- /dev/null +++ b/modbus/mb_transports/ascii/ascii_lrc.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#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; +} + diff --git a/modbus/mb_transports/ascii/ascii_lrc.h b/modbus/mb_transports/ascii/ascii_lrc.h new file mode 100644 index 0000000..b0e1563 --- /dev/null +++ b/modbus/mb_transports/ascii/ascii_lrc.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#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); \ No newline at end of file diff --git a/modbus/mb_transports/ascii/ascii_master.c b/modbus/mb_transports/ascii/ascii_master.c new file mode 100644 index 0000000..8b7fdda --- /dev/null +++ b/modbus/mb_transports/ascii/ascii_master.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_transports/ascii/ascii_slave.c b/modbus/mb_transports/ascii/ascii_slave.c new file mode 100644 index 0000000..88e8f96 --- /dev/null +++ b/modbus/mb_transports/ascii/ascii_slave.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_transports/ascii/ascii_transport.h b/modbus/mb_transports/ascii/ascii_transport.h new file mode 100644 index 0000000..128cf76 --- /dev/null +++ b/modbus/mb_transports/ascii/ascii_transport.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#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 \ No newline at end of file diff --git a/modbus/mb_transports/rtu/mbcrc.c b/modbus/mb_transports/rtu/mbcrc.c new file mode 100644 index 0000000..28bded4 --- /dev/null +++ b/modbus/mb_transports/rtu/mbcrc.c @@ -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 + * 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); +} diff --git a/modbus/mb_transports/rtu/mbcrc.h b/modbus/mb_transports/rtu/mbcrc.h new file mode 100644 index 0000000..95396fd --- /dev/null +++ b/modbus/mb_transports/rtu/mbcrc.h @@ -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 + * 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); + diff --git a/modbus/mb_transports/rtu/rtu_master.c b/modbus/mb_transports/rtu/rtu_master.c new file mode 100644 index 0000000..2e234d5 --- /dev/null +++ b/modbus/mb_transports/rtu/rtu_master.c @@ -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 diff --git a/modbus/mb_transports/rtu/rtu_slave.c b/modbus/mb_transports/rtu/rtu_slave.c new file mode 100644 index 0000000..4254571 --- /dev/null +++ b/modbus/mb_transports/rtu/rtu_slave.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_transports/rtu/rtu_transport.h b/modbus/mb_transports/rtu/rtu_transport.h new file mode 100644 index 0000000..4c997a1 --- /dev/null +++ b/modbus/mb_transports/rtu/rtu_transport.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#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 \ No newline at end of file diff --git a/modbus/mb_transports/tcp/tcp_master.c b/modbus/mb_transports/tcp/tcp_master.c new file mode 100644 index 0000000..2799348 --- /dev/null +++ b/modbus/mb_transports/tcp/tcp_master.c @@ -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 + diff --git a/modbus/mb_transports/tcp/tcp_slave.c b/modbus/mb_transports/tcp/tcp_slave.c new file mode 100644 index 0000000..86d0392 --- /dev/null +++ b/modbus/mb_transports/tcp/tcp_slave.c @@ -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 \ No newline at end of file diff --git a/modbus/mb_transports/tcp/tcp_transport.h b/modbus/mb_transports/tcp/tcp_transport.h new file mode 100644 index 0000000..6669be1 --- /dev/null +++ b/modbus/mb_transports/tcp/tcp_transport.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#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 \ No newline at end of file diff --git a/modbus/mb_transports/transport_common.h b/modbus/mb_transports/transport_common.h new file mode 100644 index 0000000..02d7f61 --- /dev/null +++ b/modbus/mb_transports/transport_common.h @@ -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 \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c194674 --- /dev/null +++ b/pytest.ini @@ -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 diff --git a/tools/ignore_build_warnings.txt b/tools/ignore_build_warnings.txt new file mode 100644 index 0000000..fe3f6c1 --- /dev/null +++ b/tools/ignore_build_warnings.txt @@ -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\)