diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index d8cdd198..91e6643b 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -328,3 +328,15 @@ jobs: gcc --version make clean make gtk-discover + + ports-esp32: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install PlatformIO + run: | + python3 -m pip install --upgrade pip + pip3 install platformio + - name: Build ESP32 Port + run: | + pio run -d ports/esp32 diff --git a/ports/esp32/.gitignore b/ports/esp32/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/ports/esp32/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/ports/esp32/README.md b/ports/esp32/README.md new file mode 100644 index 00000000..19771285 --- /dev/null +++ b/ports/esp32/README.md @@ -0,0 +1,132 @@ +# ESP32 Port + +Date: 2026-04-04 +Author: Kato Gangstad + +This folder contains the ESP32 BACnet port built with PlatformIO. +It targets multiple ESP32-family boards and covers BACnet MS/TP, BACnet/IP, and BACnet Gateway (based on apps/gateway2) profiles. +It is based on the current lwIP port and Pico port patterns in this repository. + +## Current Scope + +- BACnet MS/TP transport for ESP32 boards with RS485 +- BACnet/IP transport for compact WiFi and Ethernet ESP32 targets +- BACnet routing between BACnet/IP and BACnet MS/TP + +## Port Identity + +It is the ESP32 BACnet port for the current PlatformIO-based targets in this repository. + +## Supported Board Profiles + +The current board coverage includes: + +- Seeed Studio XIAO ESP32C3 for ultra-compact BACnet/IP +- M5StamPLC for BACnet MS/TP and BACnet router builds +- Olimex ESP32-POE for BACnet/IP + +The upstream BACnet stack has a broad source set and multiple app profiles. +This port selects the ESP32-specific transport, board, and application wiring needed for the environments documented below. + +## Files + +- src/main.cpp: Application loop, BACnet object table, and PLC I/O sync +- src/mstimer_init.c: millisecond timer hook used by mstimer +- src/rs485.c: ESP32 RS485 low-level driver for MS/TP +- src/dlenv.c: MS/TP datalink environment setup and port wiring +- src/bip_socket.cpp: BACnet/IP UDP socket bridge for WiFi and Ethernet targets +- extra_script.py: Injects BACnet core/basic sources into the PlatformIO build + +## Build Notes + +1. Open this folder as a PlatformIO project root, or run PlatformIO with this folder as cwd. +2. Start with the environment that matches your board and transport profile. +3. The `platformio.ini` file defines all tested board environments for this port. +4. Ensure PlatformIO CLI is available in your shell (`pio --version`). +5. Build with `pio run -e `. + +## Tested Environments + +The following PlatformIO environments have been built and uploaded successfully: + +- `xiao-esp32c3-wifi-bip` on Seeed Studio XIAO ESP32C3 +- `m5stamplc-mstp` on M5StamPLC +- `m5stamplc-gateway-bip-mstp` on M5StamPLC +- `esp32-poe-wifi-bip` on Olimex ESP32-POE + +## Validation Tools + +Interoperability and functional verification have been tested with: + +- YABE +- Honeywell Eaglehawk 4.15 +- Tridium Niagara 4.15 + +## Board Pictures + +The following board photos document the hardware used for the tested environments. + +### Seeed Studio XIAO ESP32C3 + +Used for: + +- `xiao-esp32c3-wifi-bip` + +Why this board stands out: + +- Extremely compact form factor for a BACnet/IP target +- Physical size: 21 x 17.8 mm +- Strong candidate for one of the smallest BACnet/IP device ever ! + +Hardware summary: + +- MCU: ESP32-C3 +- CPU type: 32-bit single-core RISC-V +- CPU clock: 160 MHz +- RAM: 320 KB +- Flash: 4 MB + +![XIAO ESP32C3](docs/board-photos/Xiao.webp) + +### M5StamPLC + +- BACnet server object model with Device + Binary Input + Binary Output objects +- Runtime mapping: + - BI 0..7 <- PLC inputs 1..8 + - BO 0..3 -> PLC relays 1..4 +- RS485 pin mapping aligned with the M5StamPLC hardware profile: + - TX: GPIO0 + - RX: GPIO39 + - DIR: GPIO46 + +Used for: + +- `m5stamplc-mstp` +- `m5stamplc-bip` +- `m5stamplc-gateway-bip-mstp` +- soon to come `m5stamplc-poe-bip` + +Hardware summary: + +- MCU: ESP32-S3 +- CPU clock: 240 MHz +- RAM: 320 KB +- Flash: 8 MB + +![M5StamPLC](docs/board-photos/M5-StamPlc.webp) + +### Olimex ESP32-POE + +Used for: + +- `esp32-poe-wifi-bip` +- `esp32-poe-eth-bip` + +Hardware summary: + +- MCU: ESP32 +- CPU clock: 240 MHz +- RAM: 320 KB +- Flash: 4 MB + +![Olimex ESP32-POE](docs/board-photos/Olimex-ESP32-POE.jpg) diff --git a/ports/esp32/boards/m5stamplc.json b/ports/esp32/boards/m5stamplc.json new file mode 100644 index 00000000..e9f553a1 --- /dev/null +++ b/ports/esp32/boards/m5stamplc.json @@ -0,0 +1,21 @@ +{ + "build": { + "core": "esp32", + "mcu": "esp32s3", + "variant": "esp32s3", + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "dio" + }, + "frameworks": [ + "arduino" + ], + "name": "M5Stack StamPLC", + "url": "https://m5stack.com/products/m5stamplc", + "vendor": "M5Stack", + "upload": { + "speed": 460800, + "maximum_size": 8388608, + "maximum_ram_size": 327680 + } +} diff --git a/ports/esp32/docs/board-photos/.gitkeep b/ports/esp32/docs/board-photos/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/ports/esp32/docs/board-photos/M5-StamPlc.webp b/ports/esp32/docs/board-photos/M5-StamPlc.webp new file mode 100644 index 00000000..08f95c1a Binary files /dev/null and b/ports/esp32/docs/board-photos/M5-StamPlc.webp differ diff --git a/ports/esp32/docs/board-photos/Olimex-ESP32-POE.jpg b/ports/esp32/docs/board-photos/Olimex-ESP32-POE.jpg new file mode 100644 index 00000000..91485b42 Binary files /dev/null and b/ports/esp32/docs/board-photos/Olimex-ESP32-POE.jpg differ diff --git a/ports/esp32/docs/board-photos/Xiao.webp b/ports/esp32/docs/board-photos/Xiao.webp new file mode 100644 index 00000000..f2458d73 Binary files /dev/null and b/ports/esp32/docs/board-photos/Xiao.webp differ diff --git a/ports/esp32/extra_script.py b/ports/esp32/extra_script.py new file mode 100644 index 00000000..f7fa5899 --- /dev/null +++ b/ports/esp32/extra_script.py @@ -0,0 +1,120 @@ +# @file +# @brief Build-time BACnet source wiring for PlatformIO environments +# @author Kato Gangstad + +Import("env") + +from pathlib import Path + +PIOENV = env.get("PIOENV", "") +if PIOENV not in ( + "m5stamplc-mstp", + "m5stamplc-bip", + "esp32-poe-wifi-bip", + "esp32-poe-eth-bip", + "xiao-esp32c3-wifi-bip", + "m5stamplc-gateway-bip-mstp", +): + Return() + +project_dir = Path(env.subst("$PROJECT_DIR")).resolve() +bacnet_src_root = (project_dir / ".." / ".." / "src").resolve() + +bacnet_common_sources = [ + "bacnet/basic/binding/address.c", + "bacnet/basic/server/bacnet_device.c", + "bacnet/basic/object/ai.c", + "bacnet/basic/object/bi.c", + "bacnet/basic/object/bo.c", + "bacnet/basic/npdu/h_npdu.c", + "bacnet/basic/service/h_apdu.c", + "bacnet/basic/service/h_dcc.c", + "bacnet/basic/service/h_noserv.c", + "bacnet/basic/service/h_rd.c", + "bacnet/basic/service/h_rp.c", + "bacnet/basic/service/h_rpm.c", + "bacnet/basic/service/h_whohas.c", + "bacnet/basic/service/h_whois.c", + "bacnet/basic/service/h_wp.c", + "bacnet/basic/service/s_iam.c", + "bacnet/basic/service/s_ihave.c", + "bacnet/basic/sys/datetime_mstimer.c", + "bacnet/basic/sys/days.c", + "bacnet/basic/sys/dst.c", + "bacnet/basic/sys/debug.c", + "bacnet/basic/sys/ringbuf.c", + "bacnet/basic/sys/fifo.c", + "bacnet/basic/sys/mstimer.c", + "bacnet/basic/tsm/tsm.c", + "bacnet/abort.c", + "bacnet/bacaction.c", + "bacnet/bacaddr.c", + "bacnet/bacapp.c", + "bacnet/bacdcode.c", + "bacnet/bacdest.c", + "bacnet/bacdevobjpropref.c", + "bacnet/bacerror.c", + "bacnet/bacint.c", + "bacnet/bacreal.c", + "bacnet/bacstr.c", + "bacnet/datetime.c", + "bacnet/dcc.c", + "bacnet/hostnport.c", + "bacnet/iam.c", + "bacnet/ihave.c", + "bacnet/indtext.c", + "bacnet/memcopy.c", + "bacnet/npdu.c", + "bacnet/proplist.c", + "bacnet/rd.c", + "bacnet/reject.c", + "bacnet/rp.c", + "bacnet/rpm.c", + "bacnet/timestamp.c", + "bacnet/whohas.c", + "bacnet/whois.c", + "bacnet/wp.c", + "bacnet/cov.c", + "bacnet/basic/sys/keylist.c", +] + +bacnet_transport_sources = { + "m5stamplc-mstp": [ + "bacnet/datalink/cobs.c", + "bacnet/datalink/crc.c", + "bacnet/datalink/dlmstp.c", + "bacnet/datalink/mstp.c", + "bacnet/datalink/mstptext.c", + ], + "m5stamplc-bip": [ + "bacnet/datalink/bvlc.c", + ], + "esp32-poe-wifi-bip": [ + "bacnet/datalink/bvlc.c", + ], + "esp32-poe-eth-bip": [ + "bacnet/datalink/bvlc.c", + ], + "xiao-esp32c3-wifi-bip": [ + "bacnet/datalink/bvlc.c", + ], + "m5stamplc-gateway-bip-mstp": [ + "bacnet/datalink/bvlc.c", + "bacnet/datalink/cobs.c", + "bacnet/datalink/crc.c", + "bacnet/datalink/dlmstp.c", + "bacnet/datalink/mstp.c", + "bacnet/datalink/mstptext.c", + ], +} + +bacnet_sources = bacnet_common_sources + bacnet_transport_sources.get(PIOENV, []) + +env.Append(CPPPATH=[str(project_dir / "src"), str(bacnet_src_root)]) + +for rel in bacnet_sources: + env.BuildSources( + str(Path("$BUILD_DIR") / "bacnet"), + str(bacnet_src_root), + src_filter=f"+<{rel}>", + ) diff --git a/ports/esp32/lib/readme.txt b/ports/esp32/lib/readme.txt deleted file mode 100644 index dbadc3d6..00000000 --- a/ports/esp32/lib/readme.txt +++ /dev/null @@ -1,36 +0,0 @@ - -This directory is intended for the project specific (private) libraries. -PlatformIO will compile them to static libraries and link to executable file. - -The source code of each library should be placed in separate directory, like -"lib/private_lib/[here are source files]". - -For example, see how can be organized `Foo` and `Bar` libraries: - -|--lib -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| |--Foo -| | |- Foo.c -| | |- Foo.h -| |- readme.txt --> THIS FILE -|- platformio.ini -|--src - |- main.c - -Then in `src/main.c` you should use: - -#include -#include - -// rest H/C/CPP code - -PlatformIO will find your libraries automatically, configure preprocessor's -include paths and build them. - -More information about PlatformIO Library Dependency Finder -- http://docs.platformio.org/page/librarymanager/ldf.html diff --git a/ports/esp32/platformio.ini b/ports/esp32/platformio.ini index a150fc69..71b39297 100644 --- a/ports/esp32/platformio.ini +++ b/ports/esp32/platformio.ini @@ -6,14 +6,155 @@ ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples -; http://docs.platformio.org/page/projectconf.html +; https://docs.platformio.org/page/projectconf.html -[env:esp32thing] +[platformio] +default_envs = m5stamplc-mstp + +[wifi_common] +build_flags = + -DWIFI_SSID=\"REPLACE_WITH_WIFI_SSID\" + -DWIFI_PASS=\"REPLACE_WITH_WIFI_PASSWORD\" + +[bip_common] +build_flags = + -UBACDL_MSTP + -DBACDL_BIP + -DBACNET_IP_PORT=47808 + -DMAX_APDU=1476 + +[env] platform = espressif32 -board = esp32thing -framework = espidf +framework = arduino +monitor_speed = 115200 +upload_speed = 921600 +extra_scripts = pre:extra_script.py +lib_deps = + m5stack/M5StamPLC@^1.2.0 +build_unflags = + -std=gnu++11 +build_flags = + -std=gnu11 + -std=gnu++17 + -I src + -I ../../src + -DBACDL_MSTP + -DMAX_TSM_TRANSACTIONS=1 + -DPRINT_ENABLED=0 + -DBACNET_BIG_ENDIAN=0 + -DCRC_USE_TABLE + -DBACAPP_MINIMAL + -DBACAPP_TIMESTAMP + -DBACNET_STACK_DEPRECATED_DISABLE + -DBACNET_PROTOCOL_REVISION=16 + -DCONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT + -DCONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT + -DCONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT -upload_port = COM7 -upload_speed = 1152000 +[env:m5stamplc-mstp] +board = m5stamplc +build_flags = + ${env.build_flags} + -DMAX_APDU=480 +build_src_filter = + + + + + + + + + + +lib_deps = m5stack/M5StamPLC@^1.2.0 -monitor_baud = 115200 +[env:m5stamplc-bip] +board = m5stamplc +build_flags = + ${env.build_flags} + ${bip_common.build_flags} + ${wifi_common.build_flags} + -DUSE_M5STAMPLC_IO=1 +build_src_filter = + + + + + + + + + + + + + + +lib_deps = m5stack/M5StamPLC@^1.2.0 + +[env:esp32-poe-wifi-bip] +board = esp32-poe +build_flags = + ${env.build_flags} + ${bip_common.build_flags} + ${wifi_common.build_flags} + -DUSE_M5STAMPLC_IO=0 + -DUSE_ETH_INTERFACE=0 +build_src_filter = + + + + + + + + + + + + + + +lib_deps = + +[env:esp32-poe-eth-bip] +board = esp32-poe +build_flags = + ${env.build_flags} + ${bip_common.build_flags} + -DUSE_M5STAMPLC_IO=0 + -DUSE_ETH_INTERFACE=1 +build_src_filter = + + + + + + + + + + + + + + +lib_deps = + +[env:xiao-esp32c3-wifi-bip] +board = seeed_xiao_esp32c3 +build_flags = + ${env.build_flags} + ${bip_common.build_flags} + ${wifi_common.build_flags} + -DUSE_M5STAMPLC_IO=0 + -DUSE_ETH_INTERFACE=0 +build_src_filter = + + + + + + + + + + + + + + +lib_deps = + +[env:m5stamplc-gateway-bip-mstp] +board = m5stamplc +build_flags = + ${env.build_flags} + ${wifi_common.build_flags} + -DMAX_APDU=480 + -UBACDL_MSTP + -DBACDL_MSTP + -DBACDL_BIP + -DBACNET_IP_PORT=47808 + -DROUTER_BIP_NET=1 + -DROUTER_MSTP_NET=200 + -DROUTER_MSTP_MAC=2 +build_src_filter = + + + + + + + + + + + + + + + + + + +lib_deps = m5stack/M5StamPLC@^1.2.0 diff --git a/ports/esp32/readme.txt b/ports/esp32/readme.txt deleted file mode 100644 index 1ecce0f1..00000000 --- a/ports/esp32/readme.txt +++ /dev/null @@ -1,67 +0,0 @@ -Bacnet Server for Espressif ESP32 - Steve Karg Bacnet stack using PlatformIO open source ecosystem for IoT development on VSCode or Atom - F. Chaxel 2017 - -TODO list : - -(Install VSCode or Atom and add the PlatformIO extension) - -Edit platformio.ini to adjust board, Com Port, ... - -Goto lib/stack and copy the requested files from Steve code : - - all .h from include directory (not all required by it's simple) - - these .c files from src or demo/handlers - abort.c - address.c - apdu.c - bacaddr.c - bacapp.c - bacdcode.c - bacerror.c - bacint.c - bacreal.c - bacstr.c - bip.c - bvlc.c - cov.c - datetime.c - bacdevobjpropref.c - dcc.c - debug.c - h_cov.c - h_ucov.c - h_npdu.c - h_rp.c - h_rpm.c - h_whois.c - h_wp.c - iam.c - hostnport.c - lighting.c - memcopy.c - noserv.c - npdu.c - proplist.c - reject.c - rp.c - rpm.c - s_iam.c - tsm.c - whois.c - wp.c - -Modify - in config.h - MAX_TSM_TRANSACTIONS 255, set the value to 10 for instances - in main.c - wifi_config to fit your wifi network - BACNET_LED 5, set another IO number depending of your board - -A lot of Warning will be issued at compile time due to the redefinition of BIT macros. -Could be removes by placing a #ifndef #BIT0 .. #endif arround the BIT macro in bits.h, -and moving to the top of include list - #include "bacnet/datalink/datalink.h" in tsm.c, s_iam and in device.c - #include "bacport.h" in bip.c and in bip.h (redondant include in bip.c) - #include "bacnet/datalink/bvlc.h" in bvlc.c diff --git a/ports/esp32/src/.clang-format b/ports/esp32/src/.clang-format deleted file mode 100644 index 579a192a..00000000 --- a/ports/esp32/src/.clang-format +++ /dev/null @@ -1,7 +0,0 @@ ---- -# Disable formatting for now as there is external code. We should move external -# code to a separate directory and enable formatting for our code. -DisableFormat: true - -# DisableFormat will not disable include sorting with some versions. -SortIncludes: Never diff --git a/ports/esp32/src/ai.c b/ports/esp32/src/ai.c deleted file mode 100644 index 8e28fcf9..00000000 --- a/ports/esp32/src/ai.c +++ /dev/null @@ -1,1294 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2005 Steve Karg - * Copyright (C) 2011 Krzysztof Malorny - * - * SPDX-License-Identifier: MIT - * - *********************************************************************/ - -/* Analog Input Objects customize for your use */ - -#include -#include -#include - -#include "bacnet/bacdef.h" -#include "bacnet/bacdcode.h" -#include "bacnet/bacenum.h" -#include "bacnet/bactext.h" -#include "bacnet/config.h" /* the custom stuff */ -#include "bacnet/basic/object/device.h" -#include "bacnet/basic/services.h" -#include "bacnet/proplist.h" -#include "bacnet/timestamp.h" -#include "bacnet/basic/object/ai.h" - -#ifndef MAX_ANALOG_INPUTS -#define MAX_ANALOG_INPUTS 2 -#endif - -ANALOG_INPUT_DESCR AI_Descr[MAX_ANALOG_INPUTS]; - -/* These three arrays are used by the ReadPropertyMultiple handler */ -static const int32_t Properties_Required[] = { PROP_OBJECT_IDENTIFIER, - PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_STATUS_FLAGS, - PROP_EVENT_STATE, PROP_OUT_OF_SERVICE, PROP_UNITS, -1 }; - -static const int32_t Properties_Optional[] = { PROP_DESCRIPTION, PROP_RELIABILITY, - PROP_COV_INCREMENT, -#if defined(INTRINSIC_REPORTING) - PROP_TIME_DELAY, PROP_NOTIFICATION_CLASS, PROP_HIGH_LIMIT, PROP_LOW_LIMIT, - PROP_DEADBAND, PROP_LIMIT_ENABLE, PROP_EVENT_ENABLE, PROP_ACKED_TRANSITIONS, - PROP_NOTIFY_TYPE, PROP_EVENT_TIME_STAMPS, -#endif - -1 }; - -static const int32_t Properties_Proprietary[] = { 9997, 9998, 9999, -1 }; - -void Analog_Input_Property_Lists( - const int32_t **pRequired, const int32_t **pOptional, const int32_t **pProprietary) -{ - if (pRequired) - *pRequired = Properties_Required; - if (pOptional) - *pOptional = Properties_Optional; - if (pProprietary) - *pProprietary = Properties_Proprietary; - - return; -} - -void Analog_Input_Init(void) -{ - unsigned i; -#if defined(INTRINSIC_REPORTING) - unsigned j; -#endif - - for (i = 0; i < MAX_ANALOG_INPUTS; i++) { - AI_Descr[i].Present_Value = 0.0f; - AI_Descr[i].Out_Of_Service = false; - AI_Descr[i].Units = UNITS_PERCENT; - AI_Descr[i].Reliability = RELIABILITY_NO_FAULT_DETECTED; - AI_Descr[i].Prior_Value = 0.0f; - AI_Descr[i].COV_Increment = 1.0f; - AI_Descr[i].Changed = false; -#if defined(INTRINSIC_REPORTING) - AI_Descr[i].Event_State = EVENT_STATE_NORMAL; - /* notification class not connected */ - AI_Descr[i].Notification_Class = BACNET_MAX_INSTANCE; - /* initialize Event time stamps using wildcards - and set Acked_transitions */ - for (j = 0; j < MAX_BACNET_EVENT_TRANSITION; j++) { - datetime_wildcard_set(&AI_Descr[i].Event_Time_Stamps[j]); - AI_Descr[i].Acked_Transitions[j].bIsAcked = true; - } - - /* Set handler for GetEventInformation function */ - handler_get_event_information_set( - OBJECT_ANALOG_INPUT, Analog_Input_Event_Information); - /* Set handler for AcknowledgeAlarm function */ - handler_alarm_ack_set(OBJECT_ANALOG_INPUT, Analog_Input_Alarm_Ack); - /* Set handler for GetAlarmSummary Service */ - handler_get_alarm_summary_set( - OBJECT_ANALOG_INPUT, Analog_Input_Alarm_Summary); -#endif - } -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need validate that the */ -/* given instance exists */ -bool Analog_Input_Valid_Instance(uint32_t object_instance) -{ - unsigned int index; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) - return true; - - return false; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then count how many you have */ -unsigned Analog_Input_Count(void) -{ - return MAX_ANALOG_INPUTS; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the instance */ -/* that correlates to the correct index */ -uint32_t Analog_Input_Index_To_Instance(unsigned index) -{ - return index; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the index */ -/* that correlates to the correct instance number */ -unsigned Analog_Input_Instance_To_Index(uint32_t object_instance) -{ - unsigned index = MAX_ANALOG_INPUTS; - - if (object_instance < MAX_ANALOG_INPUTS) - index = object_instance; - - return index; -} - -float Analog_Input_Present_Value(uint32_t object_instance) -{ - float value = 0.0; - unsigned int index; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - value = AI_Descr[index].Present_Value; - } - - return value; -} - -static void Analog_Input_COV_Detect(unsigned int index, float value) -{ - float prior_value = 0.0; - float cov_increment = 0.0; - float cov_delta = 0.0; - - if (index < MAX_ANALOG_INPUTS) { - prior_value = AI_Descr[index].Prior_Value; - cov_increment = AI_Descr[index].COV_Increment; - if (prior_value > value) { - cov_delta = prior_value - value; - } else { - cov_delta = value - prior_value; - } - if (cov_delta >= cov_increment) { - AI_Descr[index].Changed = true; - AI_Descr[index].Prior_Value = value; - } - } -} - -void Analog_Input_Present_Value_Set(uint32_t object_instance, float value) -{ - unsigned int index = 0; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - Analog_Input_COV_Detect(index, value); - AI_Descr[index].Present_Value = value; - } -} - -bool Analog_Input_Object_Name( - uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) -{ - static char text[32] = ""; - unsigned int index; - bool status = false; - - if (object_instance == 0) - status = characterstring_init_ansi(object_name, "Internal Temperature"); - else if (object_instance == 1) - status = characterstring_init_ansi(object_name, "Magnetic field"); - else { - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - snprintf(text, sizeof(text), "ANALOG INPUT %lu", (unsigned long)index); - status = characterstring_init_ansi(object_name, text); - } - } - return status; -} - -bool Analog_Input_Change_Of_Value(uint32_t object_instance) -{ - unsigned index = 0; - bool changed = false; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - changed = AI_Descr[index].Changed; - } - - return changed; -} - -void Analog_Input_Change_Of_Value_Clear(uint32_t object_instance) -{ - unsigned index = 0; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - AI_Descr[index].Changed = false; - } -} - -/** - * For a given object instance-number, loads the value_list with the COV data. - * - * @param object_instance - object-instance number of the object - * @param value_list - list of COV data - * - * @return true if the value list is encoded - */ -bool Analog_Input_Encode_Value_List( - uint32_t object_instance, BACNET_PROPERTY_VALUE *value_list) -{ - bool status = false; - - if (value_list) { - value_list->propertyIdentifier = PROP_PRESENT_VALUE; - value_list->propertyArrayIndex = BACNET_ARRAY_ALL; - value_list->value.context_specific = false; - value_list->value.tag = BACNET_APPLICATION_TAG_REAL; - value_list->value.type.Real = - Analog_Input_Present_Value(object_instance); - value_list->value.next = NULL; - value_list->priority = BACNET_NO_PRIORITY; - value_list = value_list->next; - } - if (value_list) { - value_list->propertyIdentifier = PROP_STATUS_FLAGS; - value_list->propertyArrayIndex = BACNET_ARRAY_ALL; - value_list->value.context_specific = false; - value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING; - bitstring_init(&value_list->value.type.Bit_String); - bitstring_set_bit( - &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, false); - bitstring_set_bit( - &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, false); - bitstring_set_bit( - &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN, false); - if (Analog_Input_Out_Of_Service(object_instance)) { - bitstring_set_bit(&value_list->value.type.Bit_String, - STATUS_FLAG_OUT_OF_SERVICE, true); - } else { - bitstring_set_bit(&value_list->value.type.Bit_String, - STATUS_FLAG_OUT_OF_SERVICE, false); - } - value_list->value.next = NULL; - value_list->priority = BACNET_NO_PRIORITY; - value_list->next = NULL; - status = true; - } - - return status; -} - -float Analog_Input_COV_Increment(uint32_t object_instance) -{ - unsigned index = 0; - float value = 0; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - value = AI_Descr[index].COV_Increment; - } - - return value; -} - -void Analog_Input_COV_Increment_Set(uint32_t object_instance, float value) -{ - unsigned index = 0; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - AI_Descr[index].COV_Increment = value; - Analog_Input_COV_Detect(index, AI_Descr[index].Present_Value); - } -} - -bool Analog_Input_Out_Of_Service(uint32_t object_instance) -{ - unsigned index = 0; - bool value = false; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - value = AI_Descr[index].Out_Of_Service; - } - - return value; -} - -void Analog_Input_Out_Of_Service_Set(uint32_t object_instance, bool value) -{ - unsigned index = 0; - - index = Analog_Input_Instance_To_Index(object_instance); - if (index < MAX_ANALOG_INPUTS) { - /* BACnet Testing Observed Incident oi00104 - The Changed flag was not being set when a client wrote to the - Out-of-Service bit. Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BC 135.1: 8.2.1-A BC - 135.1: 8.2.2-A Any discussions can be directed to edward@bac-test.com - Please feel free to remove this comment when my changes accepted after - suitable time for review by all interested parties. Say 6 months -> - September 2016 */ - if (AI_Descr[index].Out_Of_Service != value) { - AI_Descr[index].Changed = true; - } - AI_Descr[index].Out_Of_Service = value; - } -} - -/* return apdu length, or BACNET_STATUS_ERROR on error */ -/* assumption - object already exists */ -int Analog_Input_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) -{ - int apdu_len = 0; /* return value */ - BACNET_BIT_STRING bit_string; - BACNET_CHARACTER_STRING char_string; - ANALOG_INPUT_DESCR *CurrentAI; - unsigned object_index = 0; -#if defined(INTRINSIC_REPORTING) - unsigned i = 0; - int len = 0; -#endif - uint8_t *apdu = NULL; - - if ((rpdata == NULL) || (rpdata->application_data == NULL) || - (rpdata->application_data_len == 0)) { - return 0; - } - - object_index = Analog_Input_Instance_To_Index(rpdata->object_instance); - if (object_index < MAX_ANALOG_INPUTS) - CurrentAI = &AI_Descr[object_index]; - else - return BACNET_STATUS_ERROR; - - apdu = rpdata->application_data; - switch ((int)rpdata->object_property) { - case PROP_OBJECT_IDENTIFIER: - apdu_len = encode_application_object_id( - &apdu[0], OBJECT_ANALOG_INPUT, rpdata->object_instance); - break; - - case PROP_OBJECT_NAME: - case PROP_DESCRIPTION: - Analog_Input_Object_Name(rpdata->object_instance, &char_string); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - - case PROP_OBJECT_TYPE: - apdu_len = - encode_application_enumerated(&apdu[0], OBJECT_ANALOG_INPUT); - break; - - case PROP_PRESENT_VALUE: - apdu_len = encode_application_real( - &apdu[0], Analog_Input_Present_Value(rpdata->object_instance)); - break; - - case PROP_STATUS_FLAGS: - bitstring_init(&bit_string); -#if defined(INTRINSIC_REPORTING) - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, - CurrentAI->Event_State ? true : false); -#else - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); -#endif - bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, - CurrentAI->Out_Of_Service); - - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - - case PROP_EVENT_STATE: -#if defined(INTRINSIC_REPORTING) - apdu_len = - encode_application_enumerated(&apdu[0], CurrentAI->Event_State); -#else - apdu_len = - encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); -#endif - break; - - case PROP_RELIABILITY: - apdu_len = - encode_application_enumerated(&apdu[0], CurrentAI->Reliability); - break; - - case PROP_OUT_OF_SERVICE: - apdu_len = - encode_application_boolean(&apdu[0], CurrentAI->Out_Of_Service); - break; - - case PROP_UNITS: - apdu_len = - encode_application_enumerated(&apdu[0], CurrentAI->Units); - break; - - case PROP_COV_INCREMENT: - apdu_len = - encode_application_real(&apdu[0], CurrentAI->COV_Increment); - break; - -#if defined(INTRINSIC_REPORTING) - case PROP_TIME_DELAY: - apdu_len = - encode_application_unsigned(&apdu[0], CurrentAI->Time_Delay); - break; - - case PROP_NOTIFICATION_CLASS: - apdu_len = encode_application_unsigned( - &apdu[0], CurrentAI->Notification_Class); - break; - - case PROP_HIGH_LIMIT: - apdu_len = encode_application_real(&apdu[0], CurrentAI->High_Limit); - break; - - case PROP_LOW_LIMIT: - apdu_len = encode_application_real(&apdu[0], CurrentAI->Low_Limit); - break; - - case PROP_DEADBAND: - apdu_len = encode_application_real(&apdu[0], CurrentAI->Deadband); - break; - - case PROP_LIMIT_ENABLE: - bitstring_init(&bit_string); - bitstring_set_bit(&bit_string, 0, - (CurrentAI->Limit_Enable & EVENT_LOW_LIMIT_ENABLE) ? true - : false); - bitstring_set_bit(&bit_string, 1, - (CurrentAI->Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) ? true - : false); - - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - - case PROP_EVENT_ENABLE: - bitstring_init(&bit_string); - bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL, - (CurrentAI->Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) ? true - : false); - bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT, - (CurrentAI->Event_Enable & EVENT_ENABLE_TO_FAULT) ? true - : false); - bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL, - (CurrentAI->Event_Enable & EVENT_ENABLE_TO_NORMAL) ? true - : false); - - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - - case PROP_ACKED_TRANSITIONS: - bitstring_init(&bit_string); - bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL, - CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL].bIsAcked); - bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT, - CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked); - bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL, - CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked); - - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - - case PROP_NOTIFY_TYPE: - apdu_len = encode_application_enumerated( - &apdu[0], CurrentAI->Notify_Type ? NOTIFY_EVENT : NOTIFY_ALARM); - break; - - case PROP_EVENT_TIME_STAMPS: - /* Array element zero is the number of elements in the array */ - if (rpdata->array_index == 0) - apdu_len = encode_application_unsigned( - &apdu[0], MAX_BACNET_EVENT_TRANSITION); - /* if no index was specified, then try to encode the entire list */ - /* into one packet. */ - else if (rpdata->array_index == BACNET_ARRAY_ALL) { - for (i = 0; i < MAX_BACNET_EVENT_TRANSITION; i++) { - len = encode_opening_tag( - &apdu[apdu_len], TIME_STAMP_DATETIME); - len += encode_application_date(&apdu[apdu_len + len], - &CurrentAI->Event_Time_Stamps[i].date); - len += encode_application_time(&apdu[apdu_len + len], - &CurrentAI->Event_Time_Stamps[i].time); - len += encode_closing_tag( - &apdu[apdu_len + len], TIME_STAMP_DATETIME); - - /* add it if we have room */ - if ((apdu_len + len) < MAX_APDU) - apdu_len += len; - else { - rpdata->error_code = - ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; - apdu_len = BACNET_STATUS_ABORT; - break; - } - } - } else if (rpdata->array_index <= MAX_BACNET_EVENT_TRANSITION) { - apdu_len = - encode_opening_tag(&apdu[apdu_len], TIME_STAMP_DATETIME); - apdu_len += encode_application_date(&apdu[apdu_len], - &CurrentAI->Event_Time_Stamps[rpdata->array_index].date); - apdu_len += encode_application_time(&apdu[apdu_len], - &CurrentAI->Event_Time_Stamps[rpdata->array_index].time); - apdu_len += - encode_closing_tag(&apdu[apdu_len], TIME_STAMP_DATETIME); - } else { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif - case 9997: - /* test case for real encoding-decoding real value correctly */ - apdu_len = encode_application_real(&apdu[0], 90.510F); - break; - case 9998: - /* test case for unsigned encoding-decoding unsigned value correctly - */ - apdu_len = encode_application_unsigned(&apdu[0], 90); - break; - case 9999: - /* test case for signed encoding-decoding negative value correctly - */ - apdu_len = encode_application_signed(&apdu[0], -200); - break; - default: - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - apdu_len = BACNET_STATUS_ERROR; - break; - } - /* only array properties can have array options */ - if ((apdu_len >= 0) && - (rpdata->object_property != PROP_EVENT_TIME_STAMPS) && - (rpdata->array_index != BACNET_ARRAY_ALL)) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } - - return apdu_len; -} - -/* returns true if successful */ -bool Analog_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) -{ - bool status = false; /* return value */ - unsigned int object_index = 0; - int len = 0; - BACNET_APPLICATION_DATA_VALUE value = { 0 }; - ANALOG_INPUT_DESCR *CurrentAI; - - /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); - /* FIXME: len < application_data_len: more data? */ - if (len < 0) { - /* error while decoding - a value larger than we can handle */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - return false; - } - /* only array properties can have array options */ - if ((wp_data->object_property != PROP_EVENT_TIME_STAMPS) && - (wp_data->array_index != BACNET_ARRAY_ALL)) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } - object_index = Analog_Input_Instance_To_Index(wp_data->object_instance); - if (object_index < MAX_ANALOG_INPUTS) { - CurrentAI = &AI_Descr[object_index]; - } else { - return false; - } - - switch ((int)wp_data->object_property) { - case PROP_PRESENT_VALUE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_REAL); - if (status) { - if (CurrentAI->Out_Of_Service == true) { - Analog_Input_Present_Value_Set( - wp_data->object_instance, value.type.Real); - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - status = false; - } - } - break; - - case PROP_OUT_OF_SERVICE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_BOOLEAN); - if (status) { - Analog_Input_Out_Of_Service_Set( - wp_data->object_instance, value.type.Boolean); - } - break; - - case PROP_UNITS: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_ENUMERATED); - if (status) { - CurrentAI->Units = value.type.Enumerated; - } - break; - - case PROP_COV_INCREMENT: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_REAL); - if (status) { - if (value.type.Real >= 0.0) { - Analog_Input_COV_Increment_Set( - wp_data->object_instance, value.type.Real); - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; - -#if defined(INTRINSIC_REPORTING) - case PROP_TIME_DELAY: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - CurrentAI->Time_Delay = value.type.Unsigned_Int; - CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; - } - break; - - case PROP_NOTIFICATION_CLASS: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - CurrentAI->Notification_Class = value.type.Unsigned_Int; - } - break; - - case PROP_HIGH_LIMIT: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_REAL); - if (status) { - CurrentAI->High_Limit = value.type.Real; - } - break; - - case PROP_LOW_LIMIT: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_REAL); - if (status) { - CurrentAI->Low_Limit = value.type.Real; - } - break; - - case PROP_DEADBAND: - status = WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, - &wp_data->error_class, &wp_data->error_code); - if (status) { - CurrentAI->Deadband = value.type.Real; - } - break; - - case PROP_LIMIT_ENABLE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_BIT_STRING); - if (status) { - if (value.type.Bit_String.bits_used == 2) { - CurrentAI->Limit_Enable = value.type.Bit_String.value[0]; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - status = false; - } - } - break; - - case PROP_EVENT_ENABLE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_BIT_STRING); - if (status) { - if (value.type.Bit_String.bits_used == 3) { - CurrentAI->Event_Enable = value.type.Bit_String.value[0]; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - status = false; - } - } - break; - - case PROP_NOTIFY_TYPE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_ENUMERATED); - if (status) { - switch ((BACNET_NOTIFY_TYPE)value.type.Enumerated) { - case NOTIFY_EVENT: - CurrentAI->Notify_Type = 1; - break; - case NOTIFY_ALARM: - CurrentAI->Notify_Type = 0; - break; - default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - status = false; - break; - } - } - break; -#endif - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_NAME: - case PROP_OBJECT_TYPE: - case PROP_STATUS_FLAGS: - case PROP_EVENT_STATE: - case PROP_DESCRIPTION: - case PROP_RELIABILITY: -#if defined(INTRINSIC_REPORTING) - case PROP_ACKED_TRANSITIONS: - case PROP_EVENT_TIME_STAMPS: -#endif - case 9997: - case 9998: - case 9999: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; - default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; - } - - return status; -} - -void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) -{ -#if defined(INTRINSIC_REPORTING) - BACNET_EVENT_NOTIFICATION_DATA event_data; - BACNET_CHARACTER_STRING msgText; - ANALOG_INPUT_DESCR *CurrentAI; - unsigned int object_index; - uint8_t FromState = 0; - uint8_t ToState; - float ExceededLimit = 0.0f; - float PresentVal = 0.0f; - bool SendNotify = false; - - object_index = Analog_Input_Instance_To_Index(object_instance); - if (object_index < MAX_ANALOG_INPUTS) - CurrentAI = &AI_Descr[object_index]; - else - return; - - /* check limits */ - if (!CurrentAI->Limit_Enable) - return; /* limits are not configured */ - - if (CurrentAI->Ack_notify_data.bSendAckNotify) { - /* clean bSendAckNotify flag */ - CurrentAI->Ack_notify_data.bSendAckNotify = false; - /* copy toState */ - ToState = CurrentAI->Ack_notify_data.EventState; - -#if PRINT_ENABLED - fprintf(stderr, "Send Acknotification for (%s,%d).\n", - bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance); -#endif /* PRINT_ENABLED */ - - characterstring_init_ansi(&msgText, "AckNotification"); - - /* Notify Type */ - event_data.notifyType = NOTIFY_ACK_NOTIFICATION; - - /* Send EventNotification. */ - SendNotify = true; - } else { - /* actual Present_Value */ - PresentVal = Analog_Input_Present_Value(object_instance); - FromState = CurrentAI->Event_State; - switch (CurrentAI->Event_State) { - case EVENT_STATE_NORMAL: - /* A TO-OFFNORMAL event is generated under these conditions: - (a) the Present_Value must exceed the High_Limit for a - minimum period of time, specified in the Time_Delay property, - and (b) the HighLimitEnable flag must be set in the - Limit_Enable property, and - (c) the TO-OFFNORMAL flag must be set in the Event_Enable - property. */ - if ((PresentVal > CurrentAI->High_Limit) && - ((CurrentAI->Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) == - EVENT_HIGH_LIMIT_ENABLE) && - ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) == - EVENT_ENABLE_TO_OFFNORMAL)) { - if (!CurrentAI->Remaining_Time_Delay) - CurrentAI->Event_State = EVENT_STATE_HIGH_LIMIT; - else - CurrentAI->Remaining_Time_Delay--; - break; - } - - /* A TO-OFFNORMAL event is generated under these conditions: - (a) the Present_Value must exceed the Low_Limit plus the - Deadband for a minimum period of time, specified in the - Time_Delay property, and (b) the LowLimitEnable flag must be - set in the Limit_Enable property, and - (c) the TO-NORMAL flag must be set in the Event_Enable - property. */ - if ((PresentVal < CurrentAI->Low_Limit) && - ((CurrentAI->Limit_Enable & EVENT_LOW_LIMIT_ENABLE) == - EVENT_LOW_LIMIT_ENABLE) && - ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) == - EVENT_ENABLE_TO_OFFNORMAL)) { - if (!CurrentAI->Remaining_Time_Delay) - CurrentAI->Event_State = EVENT_STATE_LOW_LIMIT; - else - CurrentAI->Remaining_Time_Delay--; - break; - } - /* value of the object is still in the same event state */ - CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; - break; - - case EVENT_STATE_HIGH_LIMIT: - /* Once exceeded, the Present_Value must fall below the - High_Limit minus the Deadband before a TO-NORMAL event is - generated under these conditions: (a) the Present_Value must - fall below the High_Limit minus the Deadband for a minimum - period of time, specified in the Time_Delay property, and (b) - the HighLimitEnable flag must be set in the Limit_Enable - property, and (c) the TO-NORMAL flag must be set in the - Event_Enable property. */ - if ((PresentVal < - CurrentAI->High_Limit - CurrentAI->Deadband) && - ((CurrentAI->Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) == - EVENT_HIGH_LIMIT_ENABLE) && - ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_NORMAL) == - EVENT_ENABLE_TO_NORMAL)) { - if (!CurrentAI->Remaining_Time_Delay) - CurrentAI->Event_State = EVENT_STATE_NORMAL; - else - CurrentAI->Remaining_Time_Delay--; - break; - } - /* value of the object is still in the same event state */ - CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; - break; - - case EVENT_STATE_LOW_LIMIT: - /* Once the Present_Value has fallen below the Low_Limit, - the Present_Value must exceed the Low_Limit plus the Deadband - before a TO-NORMAL event is generated under these conditions: - (a) the Present_Value must exceed the Low_Limit plus the - Deadband for a minimum period of time, specified in the - Time_Delay property, and (b) the LowLimitEnable flag must be - set in the Limit_Enable property, and - (c) the TO-NORMAL flag must be set in the Event_Enable - property. */ - if ((PresentVal > CurrentAI->Low_Limit + CurrentAI->Deadband) && - ((CurrentAI->Limit_Enable & EVENT_LOW_LIMIT_ENABLE) == - EVENT_LOW_LIMIT_ENABLE) && - ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_NORMAL) == - EVENT_ENABLE_TO_NORMAL)) { - if (!CurrentAI->Remaining_Time_Delay) - CurrentAI->Event_State = EVENT_STATE_NORMAL; - else - CurrentAI->Remaining_Time_Delay--; - break; - } - /* value of the object is still in the same event state */ - CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; - break; - - default: - return; /* shouldn't happen */ - } /* switch (FromState) */ - - ToState = CurrentAI->Event_State; - - if (FromState != ToState) { - /* Event_State has changed. - Need to fill only the basic parameters of this type of event. - Other parameters will be filled in common function. */ - - switch (ToState) { - case EVENT_STATE_HIGH_LIMIT: - ExceededLimit = CurrentAI->High_Limit; - characterstring_init_ansi(&msgText, "Goes to high limit"); - break; - - case EVENT_STATE_LOW_LIMIT: - ExceededLimit = CurrentAI->Low_Limit; - characterstring_init_ansi(&msgText, "Goes to low limit"); - break; - - case EVENT_STATE_NORMAL: - if (FromState == EVENT_STATE_HIGH_LIMIT) { - ExceededLimit = CurrentAI->High_Limit; - characterstring_init_ansi( - &msgText, "Back to normal state from high limit"); - } else { - ExceededLimit = CurrentAI->Low_Limit; - characterstring_init_ansi( - &msgText, "Back to normal state from low limit"); - } - break; - - default: - ExceededLimit = 0; - break; - } /* switch (ToState) */ - -#if PRINT_ENABLED - fprintf(stderr, "Event_State for (%s,%d) goes from %s to %s.\n", - bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance, - bactext_event_state_name(FromState), - bactext_event_state_name(ToState)); -#endif /* PRINT_ENABLED */ - - /* Notify Type */ - event_data.notifyType = CurrentAI->Notify_Type; - - /* Send EventNotification. */ - SendNotify = true; - } - } - - if (SendNotify) { - /* Event Object Identifier */ - event_data.eventObjectIdentifier.type = OBJECT_ANALOG_INPUT; - event_data.eventObjectIdentifier.instance = object_instance; - - /* Time Stamp */ - event_data.timeStamp.tag = TIME_STAMP_DATETIME; - Device_getCurrentDateTime(&event_data.timeStamp.value.dateTime); - - if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) { - /* fill Event_Time_Stamps */ - switch (ToState) { - case EVENT_STATE_HIGH_LIMIT: - case EVENT_STATE_LOW_LIMIT: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_OFFNORMAL] = - event_data.timeStamp.value.dateTime; - break; - - case EVENT_STATE_FAULT: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_FAULT] = - event_data.timeStamp.value.dateTime; - break; - - case EVENT_STATE_NORMAL: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_NORMAL] = - event_data.timeStamp.value.dateTime; - break; - } - } - - /* Notification Class */ - event_data.notificationClass = CurrentAI->Notification_Class; - - /* Event Type */ - event_data.eventType = EVENT_OUT_OF_RANGE; - - /* Message Text */ - event_data.messageText = &msgText; - - /* Notify Type */ - /* filled before */ - - /* From State */ - if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) - event_data.fromState = FromState; - - /* To State */ - event_data.toState = CurrentAI->Event_State; - - /* Event Values */ - if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) { - /* Value that exceeded a limit. */ - event_data.notificationParams.outOfRange.exceedingValue = - PresentVal; - /* Status_Flags of the referenced object. */ - bitstring_init( - &event_data.notificationParams.outOfRange.statusFlags); - bitstring_set_bit( - &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_IN_ALARM, CurrentAI->Event_State ? true : false); - bitstring_set_bit( - &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_FAULT, false); - bitstring_set_bit( - &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_OVERRIDDEN, false); - bitstring_set_bit( - &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_OUT_OF_SERVICE, CurrentAI->Out_Of_Service); - /* Deadband used for limit checking. */ - event_data.notificationParams.outOfRange.deadband = - CurrentAI->Deadband; - /* Limit that was exceeded. */ - event_data.notificationParams.outOfRange.exceededLimit = - ExceededLimit; - } - - /* add data from notification class */ - Notification_Class_common_reporting_function(&event_data); - - /* Ack required */ - if ((event_data.notifyType != NOTIFY_ACK_NOTIFICATION) && - (event_data.ackRequired == true)) { - switch (event_data.toState) { - case EVENT_STATE_OFFNORMAL: - case EVENT_STATE_HIGH_LIMIT: - case EVENT_STATE_LOW_LIMIT: - CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .bIsAcked = false; - CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .Time_Stamp = event_data.timeStamp.value.dateTime; - break; - - case EVENT_STATE_FAULT: - CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked = - false; - CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT] - .Time_Stamp = event_data.timeStamp.value.dateTime; - break; - - case EVENT_STATE_NORMAL: - CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL] - .bIsAcked = false; - CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL] - .Time_Stamp = event_data.timeStamp.value.dateTime; - break; - } - } - } -#endif /* defined(INTRINSIC_REPORTING) */ -} - -#if defined(INTRINSIC_REPORTING) -int Analog_Input_Event_Information( - unsigned index, BACNET_GET_EVENT_INFORMATION_DATA *getevent_data) -{ - bool IsNotAckedTransitions; - bool IsActiveEvent; - int i; - - /* check index */ - if (index < MAX_ANALOG_INPUTS) { - /* Event_State not equal to NORMAL */ - IsActiveEvent = (AI_Descr[index].Event_State != EVENT_STATE_NORMAL); - - /* Acked_Transitions property, which has at least one of the bits - (TO-OFFNORMAL, TO-FAULT, TONORMAL) set to FALSE. */ - IsNotAckedTransitions = - (AI_Descr[index] - .Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .bIsAcked == false) | - (AI_Descr[index].Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked == - false) | - (AI_Descr[index].Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked == - false); - } else - return -1; /* end of list */ - - if ((IsActiveEvent) || (IsNotAckedTransitions)) { - /* Object Identifier */ - getevent_data->objectIdentifier.type = OBJECT_ANALOG_INPUT; - getevent_data->objectIdentifier.instance = - Analog_Input_Index_To_Instance(index); - /* Event State */ - getevent_data->eventState = AI_Descr[index].Event_State; - /* Acknowledged Transitions */ - bitstring_init(&getevent_data->acknowledgedTransitions); - bitstring_set_bit(&getevent_data->acknowledgedTransitions, - TRANSITION_TO_OFFNORMAL, - AI_Descr[index] - .Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .bIsAcked); - bitstring_set_bit(&getevent_data->acknowledgedTransitions, - TRANSITION_TO_FAULT, - AI_Descr[index].Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked); - bitstring_set_bit(&getevent_data->acknowledgedTransitions, - TRANSITION_TO_NORMAL, - AI_Descr[index].Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked); - /* Event Time Stamps */ - for (i = 0; i < 3; i++) { - getevent_data->eventTimeStamps[i].tag = TIME_STAMP_DATETIME; - getevent_data->eventTimeStamps[i].value.dateTime = - AI_Descr[index].Event_Time_Stamps[i]; - } - /* Notify Type */ - getevent_data->notifyType = AI_Descr[index].Notify_Type; - /* Event Enable */ - bitstring_init(&getevent_data->eventEnable); - bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_OFFNORMAL, - (AI_Descr[index].Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) ? true - : false); - bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_FAULT, - (AI_Descr[index].Event_Enable & EVENT_ENABLE_TO_FAULT) ? true - : false); - bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_NORMAL, - (AI_Descr[index].Event_Enable & EVENT_ENABLE_TO_NORMAL) ? true - : false); - /* Event Priorities */ - Notification_Class_Get_Priorities( - AI_Descr[index].Notification_Class, getevent_data->eventPriorities); - - return 1; /* active event */ - } else - return 0; /* no active event at this index */ -} - -int Analog_Input_Alarm_Ack( - BACNET_ALARM_ACK_DATA *alarmack_data, BACNET_ERROR_CODE *error_code) -{ - ANALOG_INPUT_DESCR *CurrentAI; - unsigned int object_index; - - object_index = Analog_Input_Instance_To_Index( - alarmack_data->eventObjectIdentifier.instance); - - if (object_index < MAX_ANALOG_INPUTS) - CurrentAI = &AI_Descr[object_index]; - else { - *error_code = ERROR_CODE_UNKNOWN_OBJECT; - return -1; - } - - switch (alarmack_data->eventStateAcked) { - case EVENT_STATE_OFFNORMAL: - case EVENT_STATE_HIGH_LIMIT: - case EVENT_STATE_LOW_LIMIT: - if (CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .bIsAcked == false) { - if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - if (datetime_compare( - &CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .Time_Stamp, - &alarmack_data->eventTimeStamp.value.dateTime) > 0) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - - /* FIXME: Send ack notification */ - CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL].bIsAcked = - true; - } else { - *error_code = ERROR_CODE_INVALID_EVENT_STATE; - return -1; - } - break; - - case EVENT_STATE_FAULT: - if (CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked == - false) { - if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - if (datetime_compare( - &CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT] - .Time_Stamp, - &alarmack_data->eventTimeStamp.value.dateTime) > 0) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - - /* FIXME: Send ack notification */ - CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked = - true; - } else { - *error_code = ERROR_CODE_INVALID_EVENT_STATE; - return -1; - } - break; - - case EVENT_STATE_NORMAL: - if (CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked == - false) { - if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - if (datetime_compare( - &CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL] - .Time_Stamp, - &alarmack_data->eventTimeStamp.value.dateTime) > 0) { - *error_code = ERROR_CODE_INVALID_TIME_STAMP; - return -1; - } - - /* FIXME: Send ack notification */ - CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked = - true; - } else { - *error_code = ERROR_CODE_INVALID_EVENT_STATE; - return -1; - } - break; - - default: - return -2; - } - CurrentAI->Ack_notify_data.bSendAckNotify = true; - CurrentAI->Ack_notify_data.EventState = alarmack_data->eventStateAcked; - - return 1; -} - -int Analog_Input_Alarm_Summary( - unsigned index, BACNET_GET_ALARM_SUMMARY_DATA *getalarm_data) -{ - /* check index */ - if (index < MAX_ANALOG_INPUTS) { - /* Event_State is not equal to NORMAL and - Notify_Type property value is ALARM */ - if ((AI_Descr[index].Event_State != EVENT_STATE_NORMAL) && - (AI_Descr[index].Notify_Type == NOTIFY_ALARM)) { - /* Object Identifier */ - getalarm_data->objectIdentifier.type = OBJECT_ANALOG_INPUT; - getalarm_data->objectIdentifier.instance = - Analog_Input_Index_To_Instance(index); - /* Alarm State */ - getalarm_data->alarmState = AI_Descr[index].Event_State; - /* Acknowledged Transitions */ - bitstring_init(&getalarm_data->acknowledgedTransitions); - bitstring_set_bit(&getalarm_data->acknowledgedTransitions, - TRANSITION_TO_OFFNORMAL, - AI_Descr[index] - .Acked_Transitions[TRANSITION_TO_OFFNORMAL] - .bIsAcked); - bitstring_set_bit(&getalarm_data->acknowledgedTransitions, - TRANSITION_TO_FAULT, - AI_Descr[index] - .Acked_Transitions[TRANSITION_TO_FAULT] - .bIsAcked); - bitstring_set_bit(&getalarm_data->acknowledgedTransitions, - TRANSITION_TO_NORMAL, - AI_Descr[index] - .Acked_Transitions[TRANSITION_TO_NORMAL] - .bIsAcked); - - return 1; /* active alarm */ - } else - return 0; /* no active alarm at this index */ - } else - return -1; /* end of list */ -} -#endif /* defined(INTRINSIC_REPORTING) */ diff --git a/ports/esp32/src/ai.h b/ports/esp32/src/ai.h deleted file mode 100644 index d19628c3..00000000 --- a/ports/esp32/src/ai.h +++ /dev/null @@ -1,151 +0,0 @@ -/************************************************************************** -* -* Copyright (C) 2005 Steve Karg -* Copyright (C) 2011 Krzysztof Malorny -* -* SPDX-License-Identifier: MIT -* -*********************************************************************/ -#ifndef AI_H -#define AI_H - -#include -#include -#include "bacnet/bacdef.h" -#include "bacnet/rp.h" -#include "bacnet/wp.h" -#if defined(INTRINSIC_REPORTING) -#include "bacnet/basic/object/nc.h" -#include "bacnet/getevent.h" -#include "bacnet/alarm_ack.h" -#include "bacnet/get_alarm_sum.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - - typedef struct analog_input_descr { - unsigned Event_State:3; - float Present_Value; - BACNET_RELIABILITY Reliability; - bool Out_Of_Service; - uint8_t Units; - float Prior_Value; - float COV_Increment; - bool Changed; -#if defined(INTRINSIC_REPORTING) - uint32_t Time_Delay; - uint32_t Notification_Class; - float High_Limit; - float Low_Limit; - float Deadband; - unsigned Limit_Enable:2; - unsigned Event_Enable:3; - unsigned Notify_Type:1; - ACKED_INFO Acked_Transitions[MAX_BACNET_EVENT_TRANSITION]; - BACNET_DATE_TIME Event_Time_Stamps[MAX_BACNET_EVENT_TRANSITION]; - /* time to generate event notification */ - uint32_t Remaining_Time_Delay; - /* AckNotification informations */ - ACK_NOTIFICATION Ack_notify_data; -#endif - } ANALOG_INPUT_DESCR; - - void Analog_Input_Property_Lists( - const int32_t **pRequired, - const int32_t **pOptional, - const int32_t **pProprietary); - - bool Analog_Input_Valid_Instance( - uint32_t object_instance); - unsigned Analog_Input_Count( - void); - uint32_t Analog_Input_Index_To_Instance( - unsigned index); - unsigned Analog_Input_Instance_To_Index( - uint32_t instance); - bool Analog_Input_Object_Instance_Add( - uint32_t instance); - - bool Analog_Input_Object_Name( - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - bool Analog_Input_Name_Set( - uint32_t object_instance, - const char *new_name); - - const char *Analog_Input_Description( - uint32_t instance); - bool Analog_Input_Description_Set( - uint32_t instance, - const char *new_name); - - bool Analog_Input_Units_Set( - uint32_t instance, - uint16_t units); - uint16_t Analog_Input_Units( - uint32_t instance); - - int Analog_Input_Read_Property( - BACNET_READ_PROPERTY_DATA * rpdata); - bool Analog_Input_Write_Property( - BACNET_WRITE_PROPERTY_DATA * wp_data); - - float Analog_Input_Present_Value( - uint32_t object_instance); - void Analog_Input_Present_Value_Set( - uint32_t object_instance, - float value); - - bool Analog_Input_Out_Of_Service( - uint32_t object_instance); - void Analog_Input_Out_Of_Service_Set( - uint32_t object_instance, - bool oos_flag); - - bool Analog_Input_Change_Of_Value( - uint32_t instance); - void Analog_Input_Change_Of_Value_Clear( - uint32_t instance); - bool Analog_Input_Encode_Value_List( - uint32_t object_instance, - BACNET_PROPERTY_VALUE * value_list); - float Analog_Input_COV_Increment( - uint32_t instance); - void Analog_Input_COV_Increment_Set( - uint32_t instance, - float value); - - /* note: header of Intrinsic_Reporting function is required - even when INTRINSIC_REPORTING is not defined */ - void Analog_Input_Intrinsic_Reporting( - uint32_t object_instance); - -#if defined(INTRINSIC_REPORTING) - int Analog_Input_Event_Information( - unsigned index, - BACNET_GET_EVENT_INFORMATION_DATA * getevent_data); - - int Analog_Input_Alarm_Ack( - BACNET_ALARM_ACK_DATA * alarmack_data, - BACNET_ERROR_CODE * error_code); - - int Analog_Input_Alarm_Summary( - unsigned index, - BACNET_GET_ALARM_SUMMARY_DATA * getalarm_data); -#endif - - uint32_t Analog_Input_Create( - uint32_t object_instance); - bool Analog_Input_Delete( - uint32_t object_instance); - void Analog_Input_Cleanup( - void); - void Analog_Input_Init( - void); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif diff --git a/ports/esp32/src/bacnet_app.c b/ports/esp32/src/bacnet_app.c new file mode 100644 index 00000000..86169d9d --- /dev/null +++ b/ports/esp32/src/bacnet_app.c @@ -0,0 +1,365 @@ +/** + * @file + * @brief BACnet application glue for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#include +#include + +#include "bacnet_app.h" +#include "mstimer_init.h" + +#if defined(BACDL_MSTP) +#include "dlenv.h" +#include "bacnet/datalink/datalink.h" +#elif defined(BACDL_BIP) +#include "bip.h" +#endif + +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/ai.h" +#include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/bo.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/npdu.h" + +#ifndef BACNET_IP_PORT +#define BACNET_IP_PORT 47808 +#endif + +#if defined(BACDL_BIP) +static uint8_t PDUBuffer[BIP_MPDU_MAX]; +#else +static uint8_t PDUBuffer[MAX_MPDU + 16]; +#endif + +#define PLC_INPUT_COUNT 8 +#define PLC_RELAY_COUNT 4 +#define PLC_ANALOG_COUNT 2 + +#define PLC_AI_INSTANCE_TEMPERATURE 100 +#define PLC_AI_INSTANCE_FREE_HEAP_KB 101 + +static uint32_t Plc_Input_Instances[PLC_INPUT_COUNT]; +static uint32_t Plc_Relay_Instances[PLC_RELAY_COUNT]; +static uint32_t Plc_Analog_Instances[PLC_ANALOG_COUNT]; + +static const char *const Plc_Input_Names[PLC_INPUT_COUNT] = { + "PLC Input 1", "PLC Input 2", "PLC Input 3", "PLC Input 4", + "PLC Input 5", "PLC Input 6", "PLC Input 7", "PLC Input 8", +}; + +static const char *const Plc_Input_Descriptions[PLC_INPUT_COUNT] = { + "PLC Input 1", "PLC Input 2", "PLC Input 3", "PLC Input 4", + "PLC Input 5", "PLC Input 6", "PLC Input 7", "PLC Input 8", +}; + +static const char *const Plc_Relay_Names[PLC_RELAY_COUNT] = { + "PLC Relay 1", + "PLC Relay 2", + "PLC Relay 3", + "PLC Relay 4", +}; + +static const char *const Plc_Relay_Descriptions[PLC_RELAY_COUNT] = { + "PLC Relay 1", + "PLC Relay 2", + "PLC Relay 3", + "PLC Relay 4", +}; + +static const char *const Plc_Analog_Names[PLC_ANALOG_COUNT] = { + "PLC Temperature", + "Free Heap KB", +}; + +static const char *const Plc_Analog_Descriptions[PLC_ANALOG_COUNT] = { + "Internal PLC temperature in C", + "Free heap memory in kilobytes", +}; + +static object_functions_t My_Object_Table[] = { + { OBJECT_DEVICE, + NULL, + Device_Count, + Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, + Device_Read_Property_Local, + Device_Write_Property_Local, + Device_Property_Lists, + DeviceGetRRInfo, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + Device_Writable_Property_List }, + + { OBJECT_BINARY_INPUT, + Binary_Input_Init, + Binary_Input_Count, + Binary_Input_Index_To_Instance, + Binary_Input_Valid_Instance, + Binary_Input_Object_Name, + Binary_Input_Read_Property, + NULL, + Binary_Input_Property_Lists, + NULL, + NULL, + Binary_Input_Encode_Value_List, + Binary_Input_Change_Of_Value, + Binary_Input_Change_Of_Value_Clear, + NULL, + NULL, + NULL, + Binary_Input_Create, + Binary_Input_Delete, + NULL, + Binary_Input_Writable_Property_List }, + + { OBJECT_ANALOG_INPUT, + Analog_Input_Init, + Analog_Input_Count, + Analog_Input_Index_To_Instance, + Analog_Input_Valid_Instance, + Analog_Input_Object_Name, + Analog_Input_Read_Property, + Analog_Input_Write_Property, + Analog_Input_Property_Lists, + NULL, + NULL, + Analog_Input_Encode_Value_List, + Analog_Input_Change_Of_Value, + Analog_Input_Change_Of_Value_Clear, + Analog_Input_Intrinsic_Reporting, + NULL, + NULL, + Analog_Input_Create, + Analog_Input_Delete, + NULL, + Analog_Input_Writable_Property_List }, + + { OBJECT_BINARY_OUTPUT, + Binary_Output_Init, + Binary_Output_Count, + Binary_Output_Index_To_Instance, + Binary_Output_Valid_Instance, + Binary_Output_Object_Name, + Binary_Output_Read_Property, + Binary_Output_Write_Property, + Binary_Output_Property_Lists, + NULL, + NULL, + Binary_Output_Encode_Value_List, + Binary_Output_Change_Of_Value, + Binary_Output_Change_Of_Value_Clear, + NULL, + NULL, + NULL, + Binary_Output_Create, + Binary_Output_Delete, + NULL, + Binary_Output_Writable_Property_List }, + + { MAX_BACNET_OBJECT_TYPE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL } +}; + +/** + * @brief Create and initialize the BACnet objects exposed by this port + */ +static void init_server_objects(void) +{ + uint32_t instance; + + Device_Init(My_Object_Table); + + for (uint8_t i = 0; i < PLC_INPUT_COUNT; i++) { + instance = Binary_Input_Create(i); + Plc_Input_Instances[i] = instance; + (void)Binary_Input_Name_Set(instance, Plc_Input_Names[i]); + (void)Binary_Input_Description_Set(instance, Plc_Input_Descriptions[i]); + } + + for (uint8_t i = 0; i < PLC_RELAY_COUNT; i++) { + instance = Binary_Output_Create(i); + Plc_Relay_Instances[i] = instance; + (void)Binary_Output_Name_Set(instance, Plc_Relay_Names[i]); + (void)Binary_Output_Description_Set( + instance, Plc_Relay_Descriptions[i]); + (void)Binary_Output_Present_Value_Set( + instance, BINARY_INACTIVE, BACNET_MAX_PRIORITY); + } + + Plc_Analog_Instances[0] = Analog_Input_Create(PLC_AI_INSTANCE_TEMPERATURE); + Plc_Analog_Instances[1] = Analog_Input_Create(PLC_AI_INSTANCE_FREE_HEAP_KB); + + (void)Analog_Input_Name_Set(Plc_Analog_Instances[0], Plc_Analog_Names[0]); + (void)Analog_Input_Description_Set( + Plc_Analog_Instances[0], Plc_Analog_Descriptions[0]); + (void)Analog_Input_Units_Set( + Plc_Analog_Instances[0], UNITS_DEGREES_CELSIUS); + + (void)Analog_Input_Name_Set(Plc_Analog_Instances[1], Plc_Analog_Names[1]); + (void)Analog_Input_Description_Set( + Plc_Analog_Instances[1], Plc_Analog_Descriptions[1]); + (void)Analog_Input_Units_Set(Plc_Analog_Instances[1], UNITS_NO_UNITS); +} + +/** + * @brief Initialize the BACnet application and selected datalink + */ +void bacnet_app_init(void) +{ + systimer_init(); + + address_init(); + Device_Set_Object_Instance_Number(12345); + init_server_objects(); + +#if defined(BACDL_MSTP) + if (!m5_dlenv_init(2)) { + for (;;) { } + } +#elif defined(BACDL_BIP) + if (!bip_init((uint16_t)BACNET_IP_PORT)) { + for (;;) { } + } +#endif + + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + + Send_I_Am(&Handler_Transmit_Buffer[0]); +} + +/** + * @brief Process one BACnet polling cycle and advance timers + */ +void bacnet_app_tick(void) +{ + BACNET_ADDRESS src = { 0 }; + uint16_t pdu_len = 0; + +#if defined(BACDL_BIP) + pdu_len = bip_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0); +#else + pdu_len = datalink_receive(&src, &PDUBuffer[0], MAX_MPDU, 0); +#endif + if (pdu_len) { + npdu_handler(&src, &PDUBuffer[0], pdu_len); + } + + tsm_timer_milliseconds(1); +} + +/** + * @brief Get the number of exposed PLC binary inputs + * @return number of binary inputs + */ +uint8_t bacnet_app_input_count(void) +{ + return PLC_INPUT_COUNT; +} + +/** + * @brief Get the number of exposed PLC relay outputs + * @return number of binary outputs + */ +uint8_t bacnet_app_relay_count(void) +{ + return PLC_RELAY_COUNT; +} + +/** + * @brief Update a BACnet binary input from PLC state + * @param index zero-based PLC input index + * @param active input state to publish + */ +void bacnet_app_input_set(uint8_t index, bool active) +{ + uint32_t instance; + + if (index >= PLC_INPUT_COUNT) { + return; + } + + instance = Plc_Input_Instances[index]; + + (void)Binary_Input_Present_Value_Set( + instance, active ? BINARY_ACTIVE : BINARY_INACTIVE); +} + +/** + * @brief Read the commanded state of a BACnet relay output + * @param index zero-based relay index + * @return true if the relay should be active + */ +bool bacnet_app_relay_get(uint8_t index) +{ + uint32_t instance; + + if (index >= PLC_RELAY_COUNT) { + return false; + } + + instance = Plc_Relay_Instances[index]; + + return (Binary_Output_Present_Value(instance) == BINARY_ACTIVE); +} + +/** + * @brief Update the analog input that reports PLC temperature + * @param value_celsius temperature in degrees Celsius + */ +void bacnet_app_temperature_set(float value_celsius) +{ + Analog_Input_Present_Value_Set(Plc_Analog_Instances[0], value_celsius); +} + +/** + * @brief Update the analog input that reports free heap memory + * @param value_kb free heap in kilobytes + */ +void bacnet_app_free_heap_kb_set(float value_kb) +{ + Analog_Input_Present_Value_Set(Plc_Analog_Instances[1], value_kb); +} diff --git a/ports/esp32/src/bacnet_app.h b/ports/esp32/src/bacnet_app.h new file mode 100644 index 00000000..3c7d8e0a --- /dev/null +++ b/ports/esp32/src/bacnet_app.h @@ -0,0 +1,69 @@ +/** + * @file + * @brief Public BACnet application hooks for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_BACNET_APP_H +#define M5STAMPLC_BACNET_APP_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the BACnet application and datalink bindings + */ +void bacnet_app_init(void); + +/** + * @brief Run one BACnet processing cycle + */ +void bacnet_app_tick(void); + +/** + * @brief Get the number of published PLC inputs + * @return number of PLC inputs + */ +uint8_t bacnet_app_input_count(void); + +/** + * @brief Get the number of published PLC relays + * @return number of PLC relays + */ +uint8_t bacnet_app_relay_count(void); + +/** + * @brief Push a PLC input state into the BACnet object model + * @param index zero-based input index + * @param active input state + */ +void bacnet_app_input_set(uint8_t index, bool active); + +/** + * @brief Read the requested state for a PLC relay + * @param index zero-based relay index + * @return true if the relay should be active + */ +bool bacnet_app_relay_get(uint8_t index); + +/** + * @brief Update the published PLC temperature value + * @param value_celsius temperature in degrees Celsius + */ +void bacnet_app_temperature_set(float value_celsius); + +/** + * @brief Update the published free heap value + * @param value_kb free heap in kilobytes + */ +void bacnet_app_free_heap_kb_set(float value_kb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/bip.c b/ports/esp32/src/bip.c new file mode 100644 index 00000000..cbe80a42 --- /dev/null +++ b/ports/esp32/src/bip.c @@ -0,0 +1,350 @@ +/** + * @file + * @brief BACnet/IP datalink implementation for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#include +#include +#include + +#include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bvlc.h" + +#include "bip.h" +#include "bvlc.h" + +#if PRINT_ENABLED || DEBUG +#include +#endif + +#define MAX_SOCK_NUM 8 + +static uint8_t BIP_Socket = MAX_SOCK_NUM; +static uint16_t BIP_Port = 0; +static uint8_t BIP_Address[4] = { 0, 0, 0, 0 }; +static uint8_t BIP_Broadcast_Address[4] = { 0, 0, 0, 0 }; + +/** + * @brief Convert a 4-byte IP address into a uint32_t value + * @param bip_address pointer to the 4-byte address + * @return 32-bit packed address + */ +uint32_t convertBIP_Address2uint32(const uint8_t *bip_address) +{ + return ((uint32_t)bip_address[0] << 24) | ((uint32_t)bip_address[1] << 16) | + ((uint32_t)bip_address[2] << 8) | ((uint32_t)bip_address[3]); +} + +/** + * @brief Convert a uint32_t IP address into a 4-byte array + * @param ip packed IP address + * @param address destination buffer for the 4-byte address + */ +void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address) +{ + address[0] = (uint8_t)(ip >> 24); + address[1] = (uint8_t)(ip >> 16); + address[2] = (uint8_t)(ip >> 8); + address[3] = (uint8_t)(ip >> 0); +} + +/** + * @brief Store the active socket identifier used by the BACnet/IP layer + * @param sock_fd socket identifier + */ +void bip_set_socket(uint8_t sock_fd) +{ + BIP_Socket = sock_fd; +} + +/** + * @brief Get the stored BACnet/IP socket identifier + * @return socket identifier + */ +uint8_t bip_socket(void) +{ + return BIP_Socket; +} + +/** + * @brief Check whether the BACnet/IP socket state is valid + * @return true if the stored socket identifier is valid + */ +bool bip_valid(void) +{ + return (BIP_Socket < MAX_SOCK_NUM); +} + +/** + * @brief Store the local IPv4 address used by BACnet/IP + * @param net_address pointer to the 4-byte local address + */ +void bip_set_addr(const uint8_t *net_address) +{ + uint8_t i; + + for (i = 0; i < 4; i++) { + BIP_Address[i] = net_address[i]; + } +} + +/** + * @brief Get the stored local IPv4 address + * @return pointer to the 4-byte local address + */ +uint8_t *bip_get_addr(void) +{ + return BIP_Address; +} + +/** + * @brief Store the broadcast IPv4 address used by BACnet/IP + * @param net_address pointer to the 4-byte broadcast address + */ +void bip_set_broadcast_addr(const uint8_t *net_address) +{ + uint8_t i; + + for (i = 0; i < 4; i++) { + BIP_Broadcast_Address[i] = net_address[i]; + } +} + +/** + * @brief Get the stored broadcast IPv4 address + * @return pointer to the 4-byte broadcast address + */ +uint8_t *bip_get_broadcast_addr(void) +{ + return BIP_Broadcast_Address; +} + +/** + * @brief Store the UDP port used by BACnet/IP + * @param port UDP port number + */ +void bip_set_port(uint16_t port) +{ + BIP_Port = port; +} + +/** + * @brief Get the UDP port used by BACnet/IP + * @return UDP port number + */ +uint16_t bip_get_port(void) +{ + return BIP_Port; +} + +/** + * @brief Decode a BACnet/IP MAC address into IPv4 address and port parts + * @param bac_addr source BACnet address + * @param address destination IPv4 address buffer + * @param port destination UDP port pointer + * @return number of bytes decoded from the MAC address + */ +static int bip_decode_bip_address( + const BACNET_ADDRESS *bac_addr, uint8_t *address, uint16_t *port) +{ + int len = 0; + + if (bac_addr) { + memcpy(address, &bac_addr->mac[0], 4); + memcpy(port, &bac_addr->mac[4], 2); + len = 6; + } + + return len; +} + +/** + * @brief Send a BACnet NPDU over BACnet/IP + * @param dest destination BACnet address + * @param npdu_data NPDU metadata + * @param pdu payload buffer + * @param pdu_len payload length in bytes + * @return number of bytes sent, or -1 on error + */ +int bip_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + uint8_t mtu[BIP_MPDU_MAX] = { 0 }; + int mtu_len = 0; + int bytes_sent = 0; + uint8_t address[] = { 0, 0, 0, 0 }; + uint16_t port = 0; + uint8_t i; + + (void)npdu_data; + + if (BIP_Socket >= MAX_SOCK_NUM) { + return -1; + } + + mtu[0] = BVLL_TYPE_BACNET_IP; + if ((dest->net == BACNET_BROADCAST_NETWORK) || + ((dest->net > 0) && (dest->len == 0)) || (dest->mac_len == 0)) { + for (i = 0; i < 4; i++) { + address[i] = BIP_Broadcast_Address[i]; + } + port = BIP_Port; + mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; + } else if (dest->mac_len == 6) { + bip_decode_bip_address(dest, address, &port); + mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; + } else { + return -1; + } + + mtu_len = 2; + mtu_len += encode_unsigned16(&mtu[mtu_len], (uint16_t)(pdu_len + 4)); + memcpy(&mtu[mtu_len], pdu, pdu_len); + mtu_len += (int)pdu_len; + + bytes_sent = bip_socket_send(address, port, mtu, (uint16_t)mtu_len); + + return bytes_sent; +} + +/** + * @brief Receive a BACnet/IP NPDU and strip BVLC framing + * @param src source BACnet address + * @param pdu receive buffer for NPDU data + * @param max_pdu receive buffer size + * @param timeout receive timeout in milliseconds + * @return NPDU length in bytes, or 0 if no valid frame was received + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout) +{ + int received_bytes = 0; + int max = 0; + uint16_t pdu_len = 0; + uint8_t src_addr[] = { 0, 0, 0, 0 }; + uint16_t src_port = 0; + uint16_t i = 0; + int function = 0; + + (void)timeout; + + if (BIP_Socket >= MAX_SOCK_NUM) { + return 0; + } + + received_bytes = bip_socket_receive(pdu, max_pdu, src_addr, &src_port); + if (received_bytes <= 0) { + return 0; + } + + if (pdu[0] != BVLL_TYPE_BACNET_IP) { + return 0; + } + + max = (int)max_pdu - received_bytes; + if (max > 0) { + if (max > 16) { + max = 16; + } + memset(&pdu[received_bytes], 0, (size_t)max); + } + + if (bvlc_for_non_bbmd(src_addr, &src_port, pdu, (uint16_t)received_bytes) > + 0) { + return 0; + } + + function = pico_bvlc_get_function_code(); + if ((function == BVLC_ORIGINAL_UNICAST_NPDU) || + (function == BVLC_ORIGINAL_BROADCAST_NPDU)) { + if ((convertBIP_Address2uint32(src_addr) == + convertBIP_Address2uint32(BIP_Address)) && + (src_port == BIP_Port)) { + pdu_len = 0; + } else { + src->mac_len = 6; + memcpy(&src->mac[0], &src_addr, 4); + memcpy(&src->mac[4], &src_port, 2); + (void)decode_unsigned16(&pdu[2], &pdu_len); + pdu_len -= 4; + if (pdu_len < max_pdu) { + for (i = 0; i < pdu_len; i++) { + pdu[i] = pdu[4 + i]; + } + } else { + pdu_len = 0; + } + } + } else if (function == BVLC_FORWARDED_NPDU) { + memcpy(&src_addr, &pdu[4], 4); + memcpy(&src_port, &pdu[8], 2); + if ((convertBIP_Address2uint32(src_addr) == + convertBIP_Address2uint32(BIP_Address)) && + (src_port == BIP_Port)) { + pdu_len = 0; + } else { + src->mac_len = 6; + memcpy(&src->mac[0], &src_addr, 4); + memcpy(&src->mac[4], &src_port, 2); + (void)decode_unsigned16(&pdu[2], &pdu_len); + pdu_len -= 10; + if (pdu_len < max_pdu) { + for (i = 0; i < pdu_len; i++) { + pdu[i] = pdu[10 + i]; + } + } else { + pdu_len = 0; + } + } + } + + return pdu_len; +} + +/** + * @brief Build the local BACnet/IP address structure + * @param my_address destination BACnet address + */ +void bip_get_my_address(BACNET_ADDRESS *my_address) +{ + if (my_address) { + my_address->mac_len = 6; + memcpy(&my_address->mac[0], &BIP_Address[0], 4); + memcpy(&my_address->mac[4], &BIP_Port, 2); + my_address->net = 0; + my_address->len = 0; + } +} + +/** + * @brief Build the BACnet/IP broadcast address structure + * @param dest destination BACnet address + */ +void bip_get_broadcast_address(BACNET_ADDRESS *dest) +{ + if (dest) { + dest->mac_len = 6; + memcpy(&dest->mac[0], &BIP_Broadcast_Address[0], 4); + memcpy(&dest->mac[4], &BIP_Port, 2); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; + } +} + +/** + * @brief Send a raw BACnet/IP MPDU to a specific host and port + * @param dest destination IPv4 address and port + * @param mtu raw BVLC/NPDU buffer + * @param mtu_len buffer length in bytes + * @return number of bytes sent, or -1 on error + */ +int bip_send_mpdu( + const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len) +{ + return bip_socket_send(dest->address, dest->port, mtu, mtu_len); +} diff --git a/ports/esp32/src/bip.h b/ports/esp32/src/bip.h new file mode 100644 index 00000000..4b68b7f4 --- /dev/null +++ b/ports/esp32/src/bip.h @@ -0,0 +1,226 @@ +/** + * @file + * @brief BACnet/IP datalink interface for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_BIP_H +#define M5STAMPLC_BIP_H + +#include +#include +#include + +#include "bacnet/bacdef.h" +#include "bacnet/npdu.h" +#include "bacnet/datalink/bvlc.h" + +#define BIP_HEADER_MAX (1 + 1 + 2) +#ifndef BIP_MPDU_MAX +#define BIP_MPDU_MAX (BIP_HEADER_MAX + MAX_PDU) +#endif + +#define BVLL_TYPE_BACNET_IP (0x81) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the UDP socket backend used by BACnet/IP + * @param port UDP port to bind + * @return true if the socket backend was initialized + */ +bool bip_socket_init(uint16_t port); + +/** + * @brief Send a raw UDP payload for BACnet/IP + * @param dest_addr destination IPv4 address + * @param dest_port destination UDP port + * @param mtu payload buffer + * @param mtu_len payload length + * @return number of bytes sent, or -1 on error + */ +int bip_socket_send( + const uint8_t *dest_addr, + uint16_t dest_port, + const uint8_t *mtu, + uint16_t mtu_len); +/** + * @brief Receive a raw UDP payload for BACnet/IP + * @param buf receive buffer + * @param buf_len receive buffer size + * @param src_addr source IPv4 address + * @param src_port source UDP port + * @return number of bytes received + */ +int bip_socket_receive( + uint8_t *buf, uint16_t buf_len, uint8_t *src_addr, uint16_t *src_port); + +/** + * @brief Close the UDP socket backend used by BACnet/IP + */ +void bip_socket_cleanup(void); + +/** + * @brief Read the current local IP address and subnet mask + * @param local_addr destination for the local IPv4 address + * @param netmask destination for the subnet mask + * @return true if the network information was available + */ +bool bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask); + +/** + * @brief Initialize the BACnet/IP datalink + * @param port UDP port to use + * @return true if initialization succeeded + */ +bool bip_init(uint16_t port); + +/** + * @brief Refresh the local and broadcast interface addresses + */ +void bip_set_interface(void); + +/** + * @brief Shut down the BACnet/IP datalink + */ +void bip_cleanup(void); + +/** + * @brief Convert a 4-byte address into a 32-bit value + * @param bip_address pointer to IPv4 address bytes + * @return packed address value + */ +uint32_t convertBIP_Address2uint32(const uint8_t *bip_address); + +/** + * @brief Convert a 32-bit address into a 4-byte array + * @param ip packed address value + * @param address destination buffer + */ +void convertUint32Address_2_uint8Address(uint32_t ip, uint8_t *address); + +/** + * @brief Store the current socket identifier + * @param sock_fd socket identifier + */ +void bip_set_socket(uint8_t sock_fd); + +/** + * @brief Get the current socket identifier + * @return socket identifier + */ +uint8_t bip_socket(void); + +/** + * @brief Check whether the BACnet/IP datalink is valid + * @return true if the datalink is ready to use + */ +bool bip_valid(void); + +/** + * @brief Build the broadcast BACnet/IP address + * @param dest destination address structure + */ +void bip_get_broadcast_address(BACNET_ADDRESS *dest); + +/** + * @brief Build the local BACnet/IP address + * @param my_address destination address structure + */ +void bip_get_my_address(BACNET_ADDRESS *my_address); + +/** + * @brief Send a BACnet NPDU over BACnet/IP + * @param dest destination BACnet address + * @param npdu_data NPDU metadata + * @param pdu payload buffer + * @param pdu_len payload length in bytes + * @return number of bytes sent, or -1 on error + */ +int bip_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len); + +/** + * @brief Send a raw BACnet/IP MPDU + * @param dest destination IP address and port + * @param mtu raw buffer to send + * @param mtu_len buffer length in bytes + * @return number of bytes sent, or -1 on error + */ +int bip_send_mpdu( + const BACNET_IP_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len); + +/** + * @brief Receive a BACnet/IP NPDU + * @param src source BACnet address + * @param pdu receive buffer + * @param max_pdu receive buffer size + * @param timeout receive timeout in milliseconds + * @return number of NPDU bytes received + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout); + +/** + * @brief Store the UDP port used by BACnet/IP + * @param port UDP port number + */ +void bip_set_port(uint16_t port); + +/** + * @brief Get the UDP port used by BACnet/IP + * @return UDP port number + */ +uint16_t bip_get_port(void); + +/** + * @brief Store the local IPv4 address + * @param net_address pointer to the 4-byte address + */ +void bip_set_addr(const uint8_t *net_address); + +/** + * @brief Get the local IPv4 address + * @return pointer to the stored address + */ +uint8_t *bip_get_addr(void); + +/** + * @brief Store the IPv4 broadcast address + * @param net_address pointer to the 4-byte broadcast address + */ +void bip_set_broadcast_addr(const uint8_t *net_address); + +/** + * @brief Get the stored IPv4 broadcast address + * @return pointer to the stored broadcast address + */ +uint8_t *bip_get_broadcast_addr(void); + +/** + * @brief Resolve a host name into an address when supported + * @param host_name host name to resolve + * @return resolved address value, or 0 when unsupported + */ +long bip_getaddrbyname(const char *host_name); + +/** + * @brief Enable BACnet/IP debug behavior + */ +void bip_debug_enable(void); + +/** + * @brief Disable BACnet/IP debug behavior + */ +void bip_debug_disable(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/bip_init.c b/ports/esp32/src/bip_init.c index f637094b..14ebb9dd 100644 --- a/ports/esp32/src/bip_init.c +++ b/ports/esp32/src/bip_init.c @@ -1,56 +1,96 @@ -// -// Copyleft F.Chaxel 2017 -// +/** + * @file + * @brief BACnet/IP initialization helpers for the PlatformIO ESP32 port + * @author Kato Gangstad + */ -#include "esp_log.h" -#include "esp_wifi.h" +#include +#include -#include "lwip/sockets.h" -#include "lwip/netdb.h" +#include "bip.h" -#include "bacnet/datalink/bip.h" +static bool BIP_Debug = false; -long bip_get_addr_by_name(const char *host_name) +/** + * @brief Enable BACnet/IP debug mode + */ +void bip_debug_enable(void) { + BIP_Debug = true; +} + +/** + * @brief Disable BACnet/IP debug mode + */ +void bip_debug_disable(void) +{ + BIP_Debug = false; +} + +/** + * @brief Resolve a host name into an address when supported + * @param host_name host name to resolve + * @return resolved address, or 0 when unsupported by this port + */ +long bip_getaddrbyname(const char *host_name) +{ + (void)host_name; return 0; } -void bip_set_interface(const char *ifname) +/** + * @brief Refresh the local and broadcast BACnet/IP addresses + */ +void bip_set_interface(void) { + uint8_t local_address[] = { 0, 0, 0, 0 }; + uint8_t broadcast_address[] = { 0, 0, 0, 0 }; + uint8_t netmask[] = { 0, 0, 0, 0 }; + uint8_t inverted_netmask[] = { 0, 0, 0, 0 }; + int i; + + if (!bip_get_local_network_info(local_address, netmask)) { + return; + } + + bip_set_addr(local_address); + + for (i = 0; i < 4; i++) { + inverted_netmask[i] = (uint8_t)~netmask[i]; + broadcast_address[i] = + (uint8_t)(local_address[i] | inverted_netmask[i]); + } + + bip_set_broadcast_addr(broadcast_address); } -void bip_cleanup(void) +/** + * @brief Initialize the BACnet/IP datalink for the selected UDP port + * @param port UDP port number + * @return true if initialization succeeded + */ +bool bip_init(uint16_t port) { - close(bip_socket()); - bip_set_socket(-1); -} + (void)BIP_Debug; -bool bip_init(char *ifname) -{ - tcpip_adapter_ip_info_t ip_info = { 0 }; + if (!bip_socket_init(port)) { + return false; + } - int value = 1; + bip_set_interface(); + bip_set_port(port); - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); - - bip_set_interface(ifname); - bip_set_port(0xBAC0U); - bip_set_addr(ip_info.ip.addr); - bip_set_broadcast_addr( - (ip_info.ip.addr & ip_info.netmask.addr) | (~ip_info.netmask.addr)); - - int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); - struct sockaddr_in saddr = { 0 }; - - saddr.sin_family = PF_INET; - saddr.sin_port = htons(0xBAC0U); - saddr.sin_addr.s_addr = htonl(INADDR_ANY); - bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); - - setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(value)); - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&value, sizeof(value)); - - bip_set_socket(sock); + bip_set_socket(0); return true; } + +/** + * @brief Shut down the BACnet/IP datalink + */ +void bip_cleanup(void) +{ + if (bip_valid()) { + bip_socket_cleanup(); + } +} diff --git a/ports/esp32/src/bip_socket.cpp b/ports/esp32/src/bip_socket.cpp new file mode 100644 index 00000000..01653874 --- /dev/null +++ b/ports/esp32/src/bip_socket.cpp @@ -0,0 +1,178 @@ +/** + * @file + * @brief UDP socket bridge used by BACnet/IP on the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#include +#include +#include +#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE) +#include +#endif + +extern "C" { +#include "bip.h" +} + +static WiFiUDP BipUdp; +static bool BipSocketOpen = false; + +/** + * @brief Check whether the selected network interface is up + * @return true if the interface is connected + */ +static bool network_connected(void) +{ +#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE) + return ETH.linkUp(); +#else + return (WiFi.status() == WL_CONNECTED); +#endif +} + +/** + * @brief Get the local IP address for the selected network interface + * @return local IP address + */ +static IPAddress network_local_ip(void) +{ +#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE) + return ETH.localIP(); +#else + return WiFi.localIP(); +#endif +} + +/** + * @brief Get the subnet mask for the selected network interface + * @return subnet mask + */ +static IPAddress network_subnet_mask(void) +{ +#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE) + return ETH.subnetMask(); +#else + return WiFi.subnetMask(); +#endif +} + +/** + * @brief Initialize the UDP socket used by BACnet/IP + * @param port UDP port to bind + * @return true if the socket was opened successfully + */ +extern "C" bool bip_socket_init(uint16_t port) +{ + if (!network_connected()) { + return false; + } + + BipUdp.stop(); + BipSocketOpen = (BipUdp.begin(port) == 1); + return BipSocketOpen; +} + +/** + * @brief Send a UDP packet for BACnet/IP + * @param dest_addr destination IPv4 address + * @param dest_port destination UDP port + * @param mtu payload buffer + * @param mtu_len payload length in bytes + * @return number of bytes sent, or -1 on error + */ +extern "C" int bip_socket_send( + const uint8_t *dest_addr, + uint16_t dest_port, + const uint8_t *mtu, + uint16_t mtu_len) +{ + if (!BipSocketOpen || !dest_addr || !mtu || (mtu_len == 0U)) { + return -1; + } + + IPAddress ip(dest_addr[0], dest_addr[1], dest_addr[2], dest_addr[3]); + if (!BipUdp.beginPacket(ip, dest_port)) { + return -1; + } + + size_t written = BipUdp.write(mtu, mtu_len); + if (!BipUdp.endPacket()) { + return -1; + } + + return (written == mtu_len) ? (int)written : -1; +} + +/** + * @brief Receive a UDP packet for BACnet/IP + * @param buf receive buffer + * @param buf_len receive buffer size + * @param src_addr source IPv4 address + * @param src_port source UDP port + * @return number of bytes received + */ +extern "C" int bip_socket_receive( + uint8_t *buf, uint16_t buf_len, uint8_t *src_addr, uint16_t *src_port) +{ + if (!BipSocketOpen || !buf || !src_addr || !src_port || (buf_len == 0U)) { + return 0; + } + + int packet_size = BipUdp.parsePacket(); + if (packet_size <= 0) { + return 0; + } + + IPAddress remote = BipUdp.remoteIP(); + src_addr[0] = remote[0]; + src_addr[1] = remote[1]; + src_addr[2] = remote[2]; + src_addr[3] = remote[3]; + *src_port = BipUdp.remotePort(); + + int to_read = packet_size; + if (to_read > (int)buf_len) { + to_read = (int)buf_len; + } + + int len = BipUdp.read(buf, to_read); + return (len > 0) ? len : 0; +} + +/** + * @brief Close the UDP socket used by BACnet/IP + */ +extern "C" void bip_socket_cleanup(void) +{ + BipUdp.stop(); + BipSocketOpen = false; +} + +/** + * @brief Get the local network address and subnet mask + * @param local_addr destination IPv4 address buffer + * @param netmask destination subnet mask buffer + * @return true if network information was available + */ +extern "C" bool +bip_get_local_network_info(uint8_t *local_addr, uint8_t *netmask) +{ + if (!local_addr || !netmask || !network_connected()) { + return false; + } + + IPAddress ip = network_local_ip(); + IPAddress mask = network_subnet_mask(); + local_addr[0] = ip[0]; + local_addr[1] = ip[1]; + local_addr[2] = ip[2]; + local_addr[3] = ip[3]; + + netmask[0] = mask[0]; + netmask[1] = mask[1]; + netmask[2] = mask[2]; + netmask[3] = mask[3]; + + return true; +} diff --git a/ports/esp32/src/bo.c b/ports/esp32/src/bo.c deleted file mode 100644 index 5b9b201f..00000000 --- a/ports/esp32/src/bo.c +++ /dev/null @@ -1,433 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2005 Steve Karg - * - * SPDX-License-Identifier: MIT - * - *********************************************************************/ - -/* Binary Output Objects - customize for your use */ - -#include -#include -#include -#include "bacnet/bacdef.h" -#include "bacnet/bacdcode.h" -#include "bacnet/bacenum.h" -#include "bacnet/bacapp.h" -#include "bacnet/config.h" /* the custom stuff */ -#include "bacnet/rp.h" -#include "bacnet/wp.h" -#include "bacnet/basic/object/bo.h" -#include "bacnet/basic/services.h" - -#ifndef MAX_BINARY_OUTPUTS -#define MAX_BINARY_OUTPUTS 1 -#endif - -/* When all the priorities are level null, the present value returns */ -/* the Relinquish Default value */ -#define RELINQUISH_DEFAULT BINARY_INACTIVE -/* Here is our Priority Array.*/ -static BACNET_BINARY_PV Binary_Output_Level[MAX_BINARY_OUTPUTS] - [BACNET_MAX_PRIORITY]; -/* Writable out-of-service allows others to play with our Present Value */ -/* without changing the physical output */ -static bool Out_Of_Service[MAX_BINARY_OUTPUTS]; - -/* These three arrays are used by the ReadPropertyMultiple handler */ -static const int32_t Binary_Output_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, - PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_STATUS_FLAGS, - PROP_EVENT_STATE, PROP_OUT_OF_SERVICE, PROP_POLARITY, PROP_PRIORITY_ARRAY, - PROP_RELINQUISH_DEFAULT, -1 }; - -static const int32_t Binary_Output_Properties_Optional[] = { PROP_DESCRIPTION, - PROP_ACTIVE_TEXT, PROP_INACTIVE_TEXT, -1 }; - -static const int32_t Binary_Output_Properties_Proprietary[] = { -1 }; - -void Binary_Output_Property_Lists( - const int32_t **pRequired, const int32_t **pOptional, const int32_t **pProprietary) -{ - if (pRequired) - *pRequired = Binary_Output_Properties_Required; - if (pOptional) - *pOptional = Binary_Output_Properties_Optional; - if (pProprietary) - *pProprietary = Binary_Output_Properties_Proprietary; - - return; -} - -void Binary_Output_Init(void) -{ - unsigned i, j; - static bool initialized = false; - - if (!initialized) { - initialized = true; - - /* initialize all the analog output priority arrays to NULL */ - for (i = 0; i < MAX_BINARY_OUTPUTS; i++) { - for (j = 0; j < BACNET_MAX_PRIORITY; j++) { - Binary_Output_Level[i][j] = BINARY_NULL; - } - } - } - - return; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need validate that the */ -/* given instance exists */ -bool Binary_Output_Valid_Instance(uint32_t object_instance) -{ - if (object_instance < MAX_BINARY_OUTPUTS) - return true; - - return false; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then count how many you have */ -unsigned Binary_Output_Count(void) -{ - return MAX_BINARY_OUTPUTS; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the instance */ -/* that correlates to the correct index */ -uint32_t Binary_Output_Index_To_Instance(unsigned index) -{ - return index; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the index */ -/* that correlates to the correct instance number */ -unsigned Binary_Output_Instance_To_Index(uint32_t object_instance) -{ - unsigned index = MAX_BINARY_OUTPUTS; - - if (object_instance < MAX_BINARY_OUTPUTS) - index = object_instance; - - return index; -} - -BACNET_BINARY_PV Binary_Output_Present_Value(uint32_t object_instance) -{ - BACNET_BINARY_PV value = RELINQUISH_DEFAULT; - unsigned index = 0; - unsigned i = 0; - - index = Binary_Output_Instance_To_Index(object_instance); - if (index < MAX_BINARY_OUTPUTS) { - for (i = 0; i < BACNET_MAX_PRIORITY; i++) { - if (Binary_Output_Level[index][i] != BINARY_NULL) { - value = Binary_Output_Level[index][i]; - break; - } - } - } - - return value; -} - -bool Binary_Output_Out_Of_Service(uint32_t object_instance) -{ - bool value = false; - unsigned index = 0; - - index = Binary_Output_Instance_To_Index(object_instance); - if (index < MAX_BINARY_OUTPUTS) { - value = Out_Of_Service[index]; - } - - return value; -} - -/* note: the object name must be unique within this device */ - -bool Binary_Output_Object_Name( - uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) -{ - static char text[32] = ""; - bool status = false; - - if (object_instance == 0) - status = characterstring_init_ansi(object_name, "Led"); - else { - if (object_instance < MAX_BINARY_OUTPUTS) { - snprintf(text, sizeof(text), "BINARY OUTPUT %lu", - (unsigned long)object_instance); - status = characterstring_init_ansi(object_name, text); - } - } - return status; -} - -/* return apdu len, or BACNET_STATUS_ERROR on error */ -int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) -{ - int len = 0; - int apdu_len = 0; /* return value */ - BACNET_BIT_STRING bit_string; - BACNET_CHARACTER_STRING char_string; - BACNET_BINARY_PV present_value = BINARY_INACTIVE; - BACNET_POLARITY polarity = POLARITY_NORMAL; - unsigned object_index = 0; - unsigned i = 0; - bool state = false; - uint8_t *apdu = NULL; - - if ((rpdata == NULL) || (rpdata->application_data == NULL) || - (rpdata->application_data_len == 0)) { - return 0; - } - apdu = rpdata->application_data; - switch (rpdata->object_property) { - case PROP_OBJECT_IDENTIFIER: - apdu_len = encode_application_object_id( - &apdu[0], OBJECT_BINARY_OUTPUT, rpdata->object_instance); - break; - /* note: Name and Description don't have to be the same. - You could make Description writable and different */ - case PROP_OBJECT_NAME: - case PROP_DESCRIPTION: - Binary_Output_Object_Name(rpdata->object_instance, &char_string); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_OBJECT_TYPE: - apdu_len = - encode_application_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT); - break; - case PROP_PRESENT_VALUE: - present_value = - Binary_Output_Present_Value(rpdata->object_instance); - apdu_len = encode_application_enumerated(&apdu[0], present_value); - break; - case PROP_STATUS_FLAGS: - /* note: see the details in the standard on how to use these */ - bitstring_init(&bit_string); - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - case PROP_EVENT_STATE: - /* note: see the details in the standard on how to use this */ - apdu_len = - encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); - break; - case PROP_OUT_OF_SERVICE: - object_index = - Binary_Output_Instance_To_Index(rpdata->object_instance); - state = Out_Of_Service[object_index]; - apdu_len = encode_application_boolean(&apdu[0], state); - break; - case PROP_POLARITY: - apdu_len = encode_application_enumerated(&apdu[0], polarity); - break; - case PROP_PRIORITY_ARRAY: - /* Array element zero is the number of elements in the array */ - if (rpdata->array_index == 0) - apdu_len = - encode_application_unsigned(&apdu[0], BACNET_MAX_PRIORITY); - /* if no index was specified, then try to encode the entire list */ - /* into one packet. */ - else if (rpdata->array_index == BACNET_ARRAY_ALL) { - object_index = - Binary_Output_Instance_To_Index(rpdata->object_instance); - for (i = 0; i < BACNET_MAX_PRIORITY; i++) { - /* FIXME: check if we have room before adding it to APDU */ - if (Binary_Output_Level[object_index][i] == BINARY_NULL) - len = encode_application_null(&apdu[apdu_len]); - else { - present_value = Binary_Output_Level[object_index][i]; - len = encode_application_enumerated( - &apdu[apdu_len], present_value); - } - /* add it if we have room */ - if ((apdu_len + len) < MAX_APDU) - apdu_len += len; - else { - rpdata->error_code = - ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; - apdu_len = BACNET_STATUS_ABORT; - break; - } - } - } else { - object_index = - Binary_Output_Instance_To_Index(rpdata->object_instance); - if (rpdata->array_index <= BACNET_MAX_PRIORITY) { - if (Binary_Output_Level[object_index][rpdata->array_index - - 1] == BINARY_NULL) - apdu_len = encode_application_null(&apdu[apdu_len]); - else { - present_value = - Binary_Output_Level[object_index] - [rpdata->array_index - 1]; - apdu_len = encode_application_enumerated( - &apdu[apdu_len], present_value); - } - } else { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; - } - } - - break; - case PROP_RELINQUISH_DEFAULT: - present_value = RELINQUISH_DEFAULT; - apdu_len = encode_application_enumerated(&apdu[0], present_value); - break; - case PROP_ACTIVE_TEXT: - characterstring_init_ansi(&char_string, "on"); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_INACTIVE_TEXT: - characterstring_init_ansi(&char_string, "off"); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - default: - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - apdu_len = BACNET_STATUS_ERROR; - break; - } - /* only array properties can have array options */ - if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) && - (rpdata->array_index != BACNET_ARRAY_ALL)) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } - - return apdu_len; -} - -/* returns true if successful */ -bool Binary_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) -{ - bool status = false; /* return value */ - unsigned int object_index = 0; - unsigned int priority = 0; - BACNET_BINARY_PV level = BINARY_NULL; - int len = 0; - BACNET_APPLICATION_DATA_VALUE value = { 0 }; - - /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); - /* FIXME: len < application_data_len: more data? */ - if (len < 0) { - /* error while decoding - a value larger than we can handle */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - return false; - } - if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && - (wp_data->array_index != BACNET_ARRAY_ALL)) { - /* only array properties can have array options */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } - switch (wp_data->object_property) { - case PROP_PRESENT_VALUE: - if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { - priority = wp_data->priority; - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - if (priority && (priority <= BACNET_MAX_PRIORITY) && - (priority != 6 /* reserved */) && - (value.type.Enumerated <= MAX_BINARY_PV)) { - level = (BACNET_BINARY_PV)value.type.Enumerated; - object_index = Binary_Output_Instance_To_Index( - wp_data->object_instance); - priority--; - Binary_Output_Level[object_index][priority] = level; - /* Note: you could set the physical output here if we - are the highest priority. - However, if Out of Service is TRUE, then don't set the - physical output. This comment may apply to the - main loop (i.e. check out of service before changing - output) */ - status = true; - } else if (priority == 6) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } else { - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_NULL); - if (status) { - level = BINARY_NULL; - object_index = Binary_Output_Instance_To_Index( - wp_data->object_instance); - priority = wp_data->priority; - if (priority && (priority <= BACNET_MAX_PRIORITY)) { - priority--; - Binary_Output_Level[object_index][priority] = level; - /* Note: you could set the physical output here to the - next highest priority, or to the relinquish default - if no priorities are set. However, if Out of Service - is TRUE, then don't set the physical output. This - comment may apply to the - main loop (i.e. check out of service before changing - output) */ - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - } - break; - case PROP_OUT_OF_SERVICE: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_BOOLEAN); - if (status) { - object_index = - Binary_Output_Instance_To_Index(wp_data->object_instance); - Out_Of_Service[object_index] = value.type.Boolean; - } - break; - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_NAME: - case PROP_OBJECT_TYPE: - case PROP_STATUS_FLAGS: - case PROP_RELIABILITY: - case PROP_EVENT_STATE: - case PROP_POLARITY: - case PROP_PRIORITY_ARRAY: - case PROP_RELINQUISH_DEFAULT: - case PROP_ACTIVE_TEXT: - case PROP_INACTIVE_TEXT: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; - default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; - } - - return status; -} diff --git a/ports/esp32/src/bo.h b/ports/esp32/src/bo.h deleted file mode 100644 index 82182db3..00000000 --- a/ports/esp32/src/bo.h +++ /dev/null @@ -1,118 +0,0 @@ -/************************************************************************** -* -* Copyright (C) 2005 Steve Karg -* -* SPDX-License-Identifier: MIT -* -*********************************************************************/ -#ifndef BO_H -#define BO_H - -#include -#include -#include "bacnet/bacdef.h" -#include "bacnet/bacerror.h" -#include "bacnet/rp.h" -#include "bacnet/wp.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - - void Binary_Output_Init( - void); - - void Binary_Output_Property_Lists( - const int32_t **pRequired, - const int32_t **pOptional, - const int32_t **pProprietary); - - bool Binary_Output_Valid_Instance( - uint32_t object_instance); - unsigned Binary_Output_Count( - void); - uint32_t Binary_Output_Index_To_Instance( - unsigned index); - unsigned Binary_Output_Instance_To_Index( - uint32_t instance); - bool Binary_Output_Object_Instance_Add( - uint32_t instance); - - bool Binary_Output_Object_Name( - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - bool Binary_Output_Name_Set( - uint32_t object_instance, - char *new_name); - - const char *Binary_Output_Description( - uint32_t instance); - bool Binary_Output_Description_Set( - uint32_t instance, - const char *new_name); - - const char *Binary_Output_Inactive_Text( - uint32_t instance); - bool Binary_Output_Inactive_Text_Set( - uint32_t instance, - char *new_name); - char *Binary_Output_Active_Text( - uint32_t instance); - bool Binary_Output_Active_Text_Set( - uint32_t instance, - char *new_name); - - BACNET_BINARY_PV Binary_Output_Present_Value( - uint32_t instance); - bool Binary_Output_Present_Value_Set( - uint32_t instance, - BACNET_BINARY_PV binary_value, - unsigned priority); - bool Binary_Output_Present_Value_Relinquish( - uint32_t instance, - unsigned priority); - unsigned Binary_Output_Present_Value_Priority( - uint32_t object_instance); - - BACNET_POLARITY Binary_Output_Polarity( - uint32_t instance); - bool Binary_Output_Polarity_Set( - uint32_t object_instance, - BACNET_POLARITY polarity); - - bool Binary_Output_Out_Of_Service( - uint32_t instance); - void Binary_Output_Out_Of_Service_Set( - uint32_t object_instance, - bool value); - - BACNET_BINARY_PV Binary_Output_Relinquish_Default( - uint32_t object_instance); - bool Binary_Output_Relinquish_Default_Set( - uint32_t object_instance, - BACNET_BINARY_PV value); - - bool Binary_Output_Encode_Value_List( - uint32_t object_instance, - BACNET_PROPERTY_VALUE * value_list); - bool Binary_Output_Change_Of_Value( - uint32_t instance); - void Binary_Output_Change_Of_Value_Clear( - uint32_t instance); - - int Binary_Output_Read_Property( - BACNET_READ_PROPERTY_DATA * rpdata); - bool Binary_Output_Write_Property( - BACNET_WRITE_PROPERTY_DATA * wp_data); - - uint32_t Binary_Output_Create( - uint32_t object_instance); - bool Binary_Output_Delete( - uint32_t object_instance); - void Binary_Output_Cleanup( - void); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif diff --git a/ports/esp32/src/bvlc.c b/ports/esp32/src/bvlc.c new file mode 100644 index 00000000..5549786b --- /dev/null +++ b/ports/esp32/src/bvlc.c @@ -0,0 +1,164 @@ +/** + * @file + * @brief BACnet Virtual Link Control implementation for the PlatformIO ESP32 + * port + * @author Kato Gangstad + */ + +#include +#include +#include + +#include "bip.h" +#include "bvlc.h" +#include "bacnet/bacint.h" + +#define BVLC_RESULT 0 +#define BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE 1 +#define BVLC_READ_BROADCAST_DIST_TABLE 2 +#define BVLC_READ_BROADCAST_DIST_TABLE_ACK 3 +#define BVLC_FORWARDED_NPDU 4 +#define BVLC_REGISTER_FOREIGN_DEVICE 5 +#define BVLC_READ_FOREIGN_DEVICE_TABLE 6 +#define BVLC_READ_FOREIGN_DEVICE_TABLE_ACK 7 +#define BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY 8 +#define BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK 9 +#define BVLC_ORIGINAL_UNICAST_NPDU 10 +#define BVLC_ORIGINAL_BROADCAST_NPDU 11 +#define BVLC_SECURE_BVLL 12 + +#define BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0010 +#define BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0020 +#define BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0X0030 +#define BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK 0x0040 +#define BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK 0x0050 +#define BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x0060 + +static BACNET_BVLC_RESULT BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION; +static BACNET_BVLC_FUNCTION BVLC_Function_Code = BVLC_RESULT; + +/** + * @brief Encode a BVLC result message + * @param pdu destination buffer, or NULL for length-only use + * @param result_code BVLC result code to encode + * @return encoded BVLC message length + */ +static int bvlc_encode_bvlc_result(uint8_t *pdu, BACNET_BVLC_RESULT result_code) +{ + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_RESULT; + encode_unsigned16(&pdu[2], 6); + encode_unsigned16(&pdu[4], (uint16_t)result_code); + } + + return 6; +} + +/** + * @brief Send a raw BVLC frame to the destination host + * @param dest_addr destination IPv4 address + * @param dest_port destination UDP port + * @param mtu raw BVLC frame buffer + * @param mtu_len frame length in bytes + * @return number of bytes sent, or -1 on error + */ +static int bvlc_send_mpdu( + const uint8_t *dest_addr, + const uint16_t *dest_port, + uint8_t *mtu, + uint16_t mtu_len) +{ + int bytes_sent = 0; + + if (!bip_valid()) { + return -1; + } + + bytes_sent = bip_socket_send(dest_addr, *dest_port, mtu, mtu_len); + + return bytes_sent; +} + +/** + * @brief Send a BVLC result response + * @param dest_addr destination IPv4 address + * @param dest_port destination UDP port + * @param result_code BVLC result code to report + */ +static void bvlc_send_result( + const uint8_t *dest_addr, + const uint16_t *dest_port, + BACNET_BVLC_RESULT result_code) +{ + uint8_t mtu[BIP_MPDU_MAX] = { 0 }; + uint16_t mtu_len = 0; + + mtu_len = (uint16_t)bvlc_encode_bvlc_result(&mtu[0], result_code); + bvlc_send_mpdu(dest_addr, dest_port, mtu, mtu_len); +} + +/** + * @brief Handle non-BBMD BVLC functions for this port + * @param addr source IPv4 address + * @param port source UDP port + * @param npdu received BVLC frame buffer + * @param received_bytes frame length in bytes + * @return non-zero when the frame was consumed by BVLC handling + */ +uint16_t bvlc_for_non_bbmd( + uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes) +{ + uint16_t result_code = 0; + + if (received_bytes >= 1) { + BVLC_Function_Code = npdu[1]; + switch (BVLC_Function_Code) { + case BVLC_RESULT: + if (received_bytes >= 6) { + (void)decode_unsigned16(&npdu[4], &result_code); + BVLC_Result_Code = (BACNET_BVLC_RESULT)result_code; + fprintf(stderr, "BVLC: Result Code=%d\n", BVLC_Result_Code); + result_code = 0; + } + break; + case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: + result_code = + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; + break; + case BVLC_READ_BROADCAST_DIST_TABLE: + result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK; + break; + case BVLC_REGISTER_FOREIGN_DEVICE: + result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK; + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE: + result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK; + break; + case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: + result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK; + break; + case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: + result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK; + break; + default: + break; + } + } + + if (result_code > 0) { + bvlc_send_result(addr, port, result_code); + fprintf(stderr, "BVLC: NAK code=%d\n", result_code); + } + + return result_code; +} + +/** + * @brief Get the last decoded BVLC function code + * @return BVLC function code + */ +BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void) +{ + return BVLC_Function_Code; +} diff --git a/ports/esp32/src/bvlc.h b/ports/esp32/src/bvlc.h new file mode 100644 index 00000000..239a00b2 --- /dev/null +++ b/ports/esp32/src/bvlc.h @@ -0,0 +1,46 @@ +/** + * @file + * @brief BACnet Virtual Link Control interfaces for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_BVLC_H +#define M5STAMPLC_BVLC_H + +#include + +#include "bacnet/datalink/bvlc.h" + +#define BACNET_BVLC_RESULT uint8_t +#define BACNET_BVLC_FUNCTION uint8_t + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef BIP_MPDU_MAX +#define BIP_MPDU_MAX 1506 +#endif + +/** + * @brief Handle non-BBMD BVLC functions for a received frame + * @param addr source IPv4 address + * @param port source UDP port + * @param npdu received frame buffer + * @param received_bytes frame length in bytes + * @return non-zero when the frame was consumed by BVLC handling + */ +uint16_t bvlc_for_non_bbmd( + uint8_t *addr, uint16_t *port, uint8_t *npdu, uint16_t received_bytes); + +/** + * @brief Get the most recently decoded BVLC function code + * @return BVLC function code + */ +BACNET_BVLC_FUNCTION pico_bvlc_get_function_code(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/device.c b/ports/esp32/src/device.c deleted file mode 100644 index 2281d752..00000000 --- a/ports/esp32/src/device.c +++ /dev/null @@ -1,1753 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2005,2006,2009 Steve Karg - * - * SPDX-License-Identifier: MIT - * - *********************************************************************/ - -/** @file device.c Base "class" for handling all BACnet objects belonging - * to a BACnet device, as well as Device-specific properties. */ - -#include -#include -#include /* for memmove */ -#include /* for timezone, localtime */ -#include "bacnet/datalink/datalink.h" -#include "bacnet/bacdef.h" -#include "bacnet/bacdcode.h" -#include "bacnet/bacenum.h" -#include "bacnet/bacapp.h" -#include "bacnet/config.h" /* the custom stuff */ -#include "bacnet/apdu.h" -#include "bacnet/wp.h" /* WriteProperty handling */ -#include "bacnet/rp.h" /* ReadProperty handling */ -#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */ -#include "bacnet/version.h" -#include "bacnet/basic/object/device.h" /* me */ -#include "bacnet/basic/services.h" -#include "../lib/stack/address.h" -/* os specfic includes */ -#include "bacnet/basic/sys/mstimer.h" -/* include the device object */ -#include "bacnet/basic/object/device.h" -#include "bacnet/basic/object/ai.h" -#include "bacnet/basic/object/bo.h" - -#if defined(INTRINSIC_REPORTING) -#include "bacnet/basic/object/nc.h" -#endif /* defined(INTRINSIC_REPORTING) */ -#include "bacnet/basic/object/bacfile.h" -#if defined(BAC_UCI) -#include "bacnet/basic/ucix/ucix.h" -#endif /* defined(BAC_UCI) */ - -/* Not included in time.h as specified by The Open Group */ -/* Difference from UTC and local standard time */ -long int timezone; - -/* local forward (semi-private) and external prototypes */ -int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); -bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data); -extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); -extern bool Routed_Device_Write_Property_Local( - BACNET_WRITE_PROPERTY_DATA *wp_data); - -/* may be overridden by outside table */ -static object_functions_t *Object_Table; - -static object_functions_t My_Object_Table[] = { - { OBJECT_DEVICE, NULL /* Init - don't init Device or it will recourse! */, - Device_Count, Device_Index_To_Instance, - Device_Valid_Object_Instance_Number, Device_Object_Name, - Device_Read_Property_Local, Device_Write_Property_Local, - Device_Property_Lists, DeviceGetRRInfo, NULL /* Iterator */, - NULL /* Value_Lists */, NULL, NULL, NULL /* Intrinsic Reporting */ }, - { OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, - Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, - Analog_Input_Object_Name, Analog_Input_Read_Property, - Analog_Input_Write_Property, Analog_Input_Property_Lists, - NULL /* ReadRangeInfo */, NULL /* Iterator */, - Analog_Input_Encode_Value_List, Analog_Input_Change_Of_Value, - Analog_Input_Change_Of_Value_Clear, Analog_Input_Intrinsic_Reporting }, - { OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count, - Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, - Binary_Output_Object_Name, Binary_Output_Read_Property, - Binary_Output_Write_Property, Binary_Output_Property_Lists, - NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, - NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, - { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, - NULL /* Index_To_Instance */, NULL /* Valid_Instance */, - NULL /* Object_Name */, NULL /* Read_Property */, - NULL /* Write_Property */, NULL /* Property_Lists */, - NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, - NULL /* COV */, NULL /* COV Clear */, - NULL /* Intrinsic Reporting */ } -}; -/** Glue function to let the Device object, when called by a handler, - * lookup which Object type needs to be invoked. - * @ingroup ObjHelpers - * @param Object_Type [in] The type of BACnet Object the handler wants to - * access. - * @return Pointer to the group of object helper functions that implement this - * type of Object. - */ -static struct object_functions *Device_Objects_Find_Functions( - BACNET_OBJECT_TYPE Object_Type) -{ - struct object_functions *pObject = NULL; - - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - /* handle each object type */ - if (pObject->Object_Type == Object_Type) { - return (pObject); - } - pObject++; - } - - return (NULL); -} - -/** Try to find a rr_info_function helper function for the requested object - * type. - * @ingroup ObjIntf - * - * @param object_type [in] The type of BACnet Object the handler wants to - * access. - * @return Pointer to the object helper function that implements the - * ReadRangeInfo function, Object_RR_Info, for this type of Object on - * success, else a NULL pointer if the type of Object isn't supported - * or doesn't have a ReadRangeInfo function. - */ -rr_info_function Device_Objects_RR_Info(BACNET_OBJECT_TYPE object_type) -{ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - return (pObject != NULL ? pObject->Object_RR_Info : NULL); -} - -/** For a given object type, returns the special property list. - * This function is used for ReadPropertyMultiple calls which want - * just Required, just Optional, or All properties. - * @ingroup ObjIntf - * - * @param object_type [in] The desired BACNET_OBJECT_TYPE whose properties - * are to be listed. - * @param pPropertyList [out] Reference to the structure which will, on return, - * list, separately, the Required, Optional, and Proprietary object - * properties with their counts. - */ -void Device_Objects_Property_List(BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - struct special_property_list_t *pPropertyList) -{ - struct object_functions *pObject = NULL; - - (void)object_instance; - pPropertyList->Required.pList = NULL; - pPropertyList->Optional.pList = NULL; - pPropertyList->Proprietary.pList = NULL; - - /* If we can find an entry for the required object type - * and there is an Object_List_RPM fn ptr then call it - * to populate the pointers to the individual list counters. - */ - - pObject = Device_Objects_Find_Functions(object_type); - if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) { - pObject->Object_RPM_List(&pPropertyList->Required.pList, - &pPropertyList->Optional.pList, &pPropertyList->Proprietary.pList); - } - - /* Fetch the counts if available otherwise zero them */ - pPropertyList->Required.count = pPropertyList->Required.pList == NULL - ? 0 - : property_list_count(pPropertyList->Required.pList); - - pPropertyList->Optional.count = pPropertyList->Optional.pList == NULL - ? 0 - : property_list_count(pPropertyList->Optional.pList); - - pPropertyList->Proprietary.count = pPropertyList->Proprietary.pList == NULL - ? 0 - : property_list_count(pPropertyList->Proprietary.pList); - - return; -} - -/** Commands a Device re-initialization, to a given state. - * The request's password must match for the operation to succeed. - * This implementation provides a framework, but doesn't - * actually *DO* anything. - * @note You could use a mix of states and passwords to multiple outcomes. - * @note You probably want to restart *after* the simple ack has been sent - * from the return handler, so just set a local flag here. - * @ingroup ObjIntf - * - * @param rd_data [in,out] The information from the RD request. - * On failure, the error class and code will be set. - * @return True if succeeds (password is correct), else False. - */ -bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) -{ - bool status = false; - - if (characterstring_ansi_same(&rd_data->password, "Jesus")) { - switch (rd_data->state) { - case BACNET_REINIT_COLDSTART: - case BACNET_REINIT_WARMSTART: - dcc_set_status_duration(COMMUNICATION_ENABLE, 0); - break; - case BACNET_REINIT_STARTBACKUP: - break; - case BACNET_REINIT_ENDBACKUP: - break; - case BACNET_REINIT_STARTRESTORE: - break; - case BACNET_REINIT_ENDRESTORE: - break; - case BACNET_REINIT_ABORTRESTORE: - break; - default: - break; - } - /* Note: you could use a mix of state - and password to multiple things */ - /* note: you probably want to restart *after* the - simple ack has been sent from the return handler - so just set a flag from here */ - status = true; - } else { - rd_data->error_class = ERROR_CLASS_SECURITY; - rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; - } - - return status; -} - -/* These three arrays are used by the ReadPropertyMultiple handler */ -static const int32_t Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, - PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_SYSTEM_STATUS, PROP_VENDOR_NAME, - PROP_VENDOR_IDENTIFIER, PROP_MODEL_NAME, PROP_FIRMWARE_REVISION, - PROP_APPLICATION_SOFTWARE_VERSION, PROP_PROTOCOL_VERSION, - PROP_PROTOCOL_REVISION, PROP_PROTOCOL_SERVICES_SUPPORTED, - PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, PROP_OBJECT_LIST, - PROP_MAX_APDU_LENGTH_ACCEPTED, PROP_SEGMENTATION_SUPPORTED, - PROP_APDU_TIMEOUT, PROP_NUMBER_OF_APDU_RETRIES, PROP_DEVICE_ADDRESS_BINDING, - PROP_DATABASE_REVISION, -1 }; - -static const int32_t Device_Properties_Optional[] = { -#if defined(BACDL_MSTP) - PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, -#endif - PROP_DESCRIPTION, PROP_LOCAL_TIME, PROP_UTC_OFFSET, PROP_LOCAL_DATE, - PROP_DAYLIGHT_SAVINGS_STATUS, PROP_LOCATION, PROP_ACTIVE_COV_SUBSCRIPTIONS, -#if defined(BACNET_TIME_MASTER) - PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, - PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, -#endif - -1 -}; - -static const int32_t Device_Properties_Proprietary[] = { -1 }; - -void Device_Property_Lists( - const int32_t **pRequired, const int32_t **pOptional, const int32_t **pProprietary) -{ - if (pRequired) - *pRequired = Device_Properties_Required; - if (pOptional) - *pOptional = Device_Properties_Optional; - if (pProprietary) - *pProprietary = Device_Properties_Proprietary; - - return; -} - -/* note: you really only need to define variables for - properties that are writable or that may change. - The properties that are constant can be hard coded - into the read-property encoding. */ - -static uint32_t Object_Instance_Number = 260001; -static BACNET_CHARACTER_STRING My_Object_Name; -static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; -static char *Vendor_Name = BACNET_VENDOR_NAME; -static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; -static char Model_Name[MAX_DEV_MOD_LEN + 1] = "ESP32"; -static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = "1.0"; -static char Location[MAX_DEV_LOC_LEN + 1] = "FR"; -static char Description[MAX_DEV_DESC_LEN + 1] = - "EPS32 by F. Chaxel, Stack S. Karg"; -static char *BACnet_Version = "0.8.2"; -/* static uint8_t Protocol_Version = 1; - constant, not settable */ -/* static uint8_t Protocol_Revision = 4; - constant, not settable */ -/* Protocol_Services_Supported - dynamically generated */ -/* Protocol_Object_Types_Supported - in RP encoding */ -/* Object_List - dynamically generated */ -/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */ -/* static uint8_t Max_Segments_Accepted = 0; */ -/* VT_Classes_Supported */ -/* Active_VT_Sessions */ -static BACNET_TIME Local_Time; /* rely on OS, if there is one */ -static BACNET_DATE Local_Date; /* rely on OS, if there is one */ -/* NOTE: BACnet UTC Offset is inverse of common practice. - If your UTC offset is -5hours of GMT, - then BACnet UTC offset is +5hours. - BACnet UTC offset is expressed in minutes. */ -static int32_t UTC_Offset = 5 * 60; -static bool Daylight_Savings_Status = false; /* rely on OS */ -#if defined(BACNET_TIME_MASTER) -static bool Align_Intervals; -static uint32_t Interval_Minutes; -static uint32_t Interval_Offset_Minutes; -/* Time_Synchronization_Recipients */ -#endif -/* List_Of_Session_Keys */ -/* Max_Master - rely on MS/TP subsystem, if there is one */ -/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ -/* Device_Address_Binding - required, but relies on binding cache */ -static uint32_t Database_Revision = 0; -/* Configuration_Files */ -/* Last_Restore_Time */ -/* Backup_Failure_Timeout */ -/* Active_COV_Subscriptions */ -/* Slave_Proxy_Enable */ -/* Manual_Slave_Address_Binding */ -/* Auto_Slave_Discovery */ -/* Slave_Address_Binding */ -/* Profile_Name */ - -unsigned Device_Count(void) -{ - return 1; -} - -uint32_t Device_Index_To_Instance(unsigned index) -{ - index = index; - return Object_Instance_Number; -} - -/* methods to manipulate the data */ - -/** Return the Object Instance number for our (single) Device Object. - * This is a key function, widely invoked by the handler code, since - * it provides "our" (ie, local) address. - * @ingroup ObjIntf - * @return The Instance number used in the BACNET_OBJECT_ID for the Device. - */ -uint32_t Device_Object_Instance_Number(void) -{ -#ifdef BAC_ROUTING - return Routed_Device_Object_Instance_Number(); -#else - return Object_Instance_Number; -#endif -} - -bool Device_Set_Object_Instance_Number(uint32_t object_id) -{ - bool status = true; /* return value */ - - if (object_id <= BACNET_MAX_INSTANCE) { - /* Make the change and update the database revision */ - Object_Instance_Number = object_id; - Device_Inc_Database_Revision(); - } else - status = false; - - return status; -} - -bool Device_Valid_Object_Instance_Number(uint32_t object_id) -{ - return (Object_Instance_Number == object_id); -} - -bool Device_Object_Name( - uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) -{ - bool status = false; - - if (object_instance == Object_Instance_Number) { - status = characterstring_copy(object_name, &My_Object_Name); - } - - return status; -} - -bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name) -{ - bool status = false; /*return value */ - - if (!characterstring_same(&My_Object_Name, object_name)) { - /* Make the change and update the database revision */ - status = characterstring_copy(&My_Object_Name, object_name); - Device_Inc_Database_Revision(); - } - - return status; -} - -bool Device_Object_Name_ANSI_Init(const char *value) -{ - return characterstring_init_ansi(&My_Object_Name, value); -} - -BACNET_DEVICE_STATUS Device_System_Status(void) -{ - return System_Status; -} - -int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) -{ - int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ - - /* We limit the options available depending on whether the source is - * internal or external. */ - if (local) { - switch (status) { - case STATUS_OPERATIONAL: - case STATUS_OPERATIONAL_READ_ONLY: - case STATUS_DOWNLOAD_REQUIRED: - case STATUS_DOWNLOAD_IN_PROGRESS: - case STATUS_NON_OPERATIONAL: - System_Status = status; - break; - - /* Don't support backup at present so don't allow setting */ - case STATUS_BACKUP_IN_PROGRESS: - result = -2; - break; - - default: - result = -1; - break; - } - } else { - switch (status) { - /* Allow these for the moment as a way to easily alter - * overall device operation. The lack of password protection - * or other authentication makes allowing writes to this - * property a risky facility to provide. - */ - case STATUS_OPERATIONAL: - case STATUS_OPERATIONAL_READ_ONLY: - case STATUS_NON_OPERATIONAL: - System_Status = status; - break; - - /* Don't allow outsider set this - it should probably - * be set if the device config is incomplete or - * corrupted or perhaps after some sort of operator - * wipe operation. - */ - case STATUS_DOWNLOAD_REQUIRED: - /* Don't allow outsider set this - it should be set - * internally at the start of a multi packet download - * perhaps indirectly via PT or WF to a config file. - */ - case STATUS_DOWNLOAD_IN_PROGRESS: - /* Don't support backup at present so don't allow setting */ - case STATUS_BACKUP_IN_PROGRESS: - result = -2; - break; - - default: - result = -1; - break; - } - } - - return (result); -} - -const char *Device_Vendor_Name(void) -{ - return Vendor_Name; -} - -/** Returns the Vendor ID for this Device. - * See the assignments at - * http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm - * @return The Vendor ID of this Device. - */ -uint16_t Device_Vendor_Identifier(void) -{ - return Vendor_Identifier; -} - -void Device_Set_Vendor_Identifier(uint16_t vendor_id) -{ - Vendor_Identifier = vendor_id; -} - -const char *Device_Model_Name(void) -{ - return Model_Name; -} - -bool Device_Set_Model_Name(const char *name, size_t length) -{ - bool status = false; /*return value */ - - if (length < sizeof(Model_Name)) { - memmove(Model_Name, name, length); - Model_Name[length] = 0; - status = true; - } - - return status; -} - -const char *Device_Firmware_Revision(void) -{ - return BACnet_Version; -} - -const char *Device_Application_Software_Version(void) -{ - return Application_Software_Version; -} - -bool Device_Set_Application_Software_Version(const char *name, size_t length) -{ - bool status = false; /*return value */ - - if (length < sizeof(Application_Software_Version)) { - memmove(Application_Software_Version, name, length); - Application_Software_Version[length] = 0; - status = true; - } - - return status; -} - -const char *Device_Description(void) -{ - return Description; -} - -bool Device_Set_Description(const char *name, size_t length) -{ - bool status = false; /*return value */ - - if (length < sizeof(Description)) { - memmove(Description, name, length); - Description[length] = 0; - status = true; - } - - return status; -} - -const char *Device_Location(void) -{ - return Location; -} - -bool Device_Set_Location(const char *name, size_t length) -{ - bool status = false; /*return value */ - - if (length < sizeof(Location)) { - memmove(Location, name, length); - Location[length] = 0; - status = true; - } - - return status; -} - -uint8_t Device_Protocol_Version(void) -{ - return BACNET_PROTOCOL_VERSION; -} - -uint8_t Device_Protocol_Revision(void) -{ - return BACNET_PROTOCOL_REVISION; -} - -BACNET_SEGMENTATION Device_Segmentation_Supported(void) -{ - return SEGMENTATION_NONE; -} - -uint32_t Device_Database_Revision(void) -{ - return Database_Revision; -} - -void Device_Set_Database_Revision(uint32_t revision) -{ - Database_Revision = revision; -} - -/* - * Shortcut for incrementing database revision as this is potentially - * the most common operation if changing object names and ids is - * implemented. - */ -void Device_Inc_Database_Revision(void) -{ - Database_Revision++; -} - -/** Get the total count of objects supported by this Device Object. - * @note Since many network clients depend on the object list - * for discovery, it must be consistent! - * @return The count of objects, for all supported Object types. - */ -unsigned Device_Object_List_Count(void) -{ - unsigned count = 0; /* number of objects */ - struct object_functions *pObject = NULL; - - /* initialize the default return values */ - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Count) { - count += pObject->Object_Count(); - } - pObject++; - } - - return count; -} - -/** Lookup the Object at the given array index in the Device's Object List. - * Even though we don't keep a single linear array of objects in the Device, - * this method acts as though we do and works through a virtual, concatenated - * array of all of our object type arrays. - * - * @param array_index [in] The desired array index (1 to N) - * @param object_type [out] The object's type, if found. - * @param instance [out] The object's instance number, if found. - * @return True if found, else false. - */ -bool Device_Object_List_Identifier( - uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance) -{ - bool status = false; - uint32_t count = 0; - uint32_t object_index = 0; - uint32_t temp_index = 0; - struct object_functions *pObject = NULL; - - /* array index zero is length - so invalid */ - if (array_index == 0) { - return status; - } - object_index = array_index - 1; - /* initialize the default return values */ - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Count) { - object_index -= count; - count = pObject->Object_Count(); - if (object_index < count) { - /* Use the iterator function if available otherwise - * look for the index to instance to get the ID */ - if (pObject->Object_Iterator) { - /* First find the first object */ - temp_index = pObject->Object_Iterator(~(unsigned)0); - /* Then step through the objects to find the nth */ - while (object_index != 0) { - temp_index = pObject->Object_Iterator(temp_index); - object_index--; - } - /* set the object_index up before falling through to next - * bit */ - object_index = temp_index; - } - if (pObject->Object_Index_To_Instance) { - *object_type = pObject->Object_Type; - *instance = pObject->Object_Index_To_Instance(object_index); - status = true; - break; - } - } - } - pObject++; - } - - return status; -} - -/** Determine if we have an object with the given object_name. - * If the object_type and object_instance pointers are not null, - * and the lookup succeeds, they will be given the resulting values. - * @param object_name [in] The desired Object Name to look for. - * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. - * @param object_instance [out] The object instance number of the matching - * Object. - * @return True on success or else False if not found. - */ -bool Device_Valid_Object_Name(const BACNET_CHARACTER_STRING *object_name1, - BACNET_OBJECT_TYPE *object_type, - uint32_t *object_instance) -{ - bool found = false; - BACNET_OBJECT_TYPE type = OBJECT_NONE; - uint32_t instance; - uint32_t max_objects = 0, i = 0; - bool check_id = false; - BACNET_CHARACTER_STRING object_name2; - struct object_functions *pObject = NULL; - - max_objects = Device_Object_List_Count(); - for (i = 1; i <= max_objects; i++) { - check_id = Device_Object_List_Identifier(i, &type, &instance); - if (check_id) { - pObject = Device_Objects_Find_Functions(type); - if ((pObject != NULL) && (pObject->Object_Name != NULL) && - (pObject->Object_Name(instance, &object_name2) && - characterstring_same(object_name1, &object_name2))) { - found = true; - if (object_type) { - *object_type = type; - } - if (object_instance) { - *object_instance = instance; - } - break; - } - } - } - - return found; -} - -/** Determine if we have an object of this type and instance number. - * @param object_type [in] The desired BACNET_OBJECT_TYPE - * @param object_instance [in] The object instance number to be looked up. - * @return True if found, else False if no such Object in this device. - */ -bool Device_Valid_Object_Id( - BACNET_OBJECT_TYPE object_type, uint32_t object_instance) -{ - bool status = false; /* return value */ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { - status = pObject->Object_Valid_Instance(object_instance); - } - - return status; -} - -/** Copy a child object's object_name value, given its ID. - * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object. - * @param object_instance [in] The object instance number of the child Object. - * @param object_name [out] The Object Name found for this child Object. - * @return True on success or else False if not found. - */ -bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - BACNET_CHARACTER_STRING *object_name) -{ - struct object_functions *pObject = NULL; - bool found = false; - - pObject = Device_Objects_Find_Functions(object_type); - if ((pObject != NULL) && (pObject->Object_Name != NULL)) { - found = pObject->Object_Name(object_instance, object_name); - } - - return found; -} - -static void Update_Current_Time(void) -{ - struct tm *tblock = NULL; -#if defined(_MSC_VER) - time_t tTemp; -#else - struct timeval tv; -#endif -/* -struct tm - -int tm_sec Seconds [0,60]. -int tm_min Minutes [0,59]. -int tm_hour Hour [0,23]. -int tm_mday Day of month [1,31]. -int tm_mon Month of year [0,11]. -int tm_year Years since 1900. -int tm_wday Day of week [0,6] (Sunday =0). -int tm_yday Day of year [0,365]. -int tm_isdst Daylight Savings flag. -*/ -#if defined(_MSC_VER) - time(&tTemp); - tblock = (struct tm *)localtime(&tTemp); -#else - if (gettimeofday(&tv, NULL) == 0) { - tblock = (struct tm *)localtime((const time_t *)&tv.tv_sec); - } -#endif - - if (tblock) { - datetime_set_date(&Local_Date, (uint16_t)tblock->tm_year + 1900, - (uint8_t)tblock->tm_mon + 1, (uint8_t)tblock->tm_mday); -#if !defined(_MSC_VER) - datetime_set_time(&Local_Time, (uint8_t)tblock->tm_hour, - (uint8_t)tblock->tm_min, (uint8_t)tblock->tm_sec, - (uint8_t)(tv.tv_usec / 10000)); -#else - datetime_set_time(&Local_Time, (uint8_t)tblock->tm_hour, - (uint8_t)tblock->tm_min, (uint8_t)tblock->tm_sec, 0); -#endif - if (tblock->tm_isdst) { - Daylight_Savings_Status = true; - } else { - Daylight_Savings_Status = false; - } - /* note: timezone is declared in stdlib. */ - UTC_Offset = timezone / 60; - } else { - datetime_date_wildcard_set(&Local_Date); - datetime_time_wildcard_set(&Local_Time); - Daylight_Savings_Status = false; - } -} - -void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime) -{ - Update_Current_Time(); - - DateTime->date = Local_Date; - DateTime->time = Local_Time; -} - -int32_t Device_UTC_Offset(void) -{ - Update_Current_Time(); - - return UTC_Offset; -} - -void Device_UTC_Offset_Set(int16_t offset) -{ - UTC_Offset = offset; -} - -bool Device_Daylight_Savings_Status(void) -{ - return Daylight_Savings_Status; -} - -#if defined(BACNET_TIME_MASTER) -/** - * Sets the time sync interval in minutes - * - * @param flag - * This property, of type BOOLEAN, specifies whether (TRUE) - * or not (FALSE) clock-aligned periodic time synchronization is - * enabled. If periodic time synchronization is enabled and the - * time synchronization interval is a factor of (divides without - * remainder) an hour or day, then the beginning of the period - * specified for time synchronization shall be aligned to the hour or - * day, respectively. If this property is present, it shall be writable. - */ -bool Device_Align_Intervals_Set(bool flag) -{ - Align_Intervals = flag; - - return true; -} - -bool Device_Align_Intervals(void) -{ - return Align_Intervals; -} - -/** - * Sets the time sync interval in minutes - * - * @param minutes - * This property, of type Unsigned, specifies the periodic - * interval in minutes at which TimeSynchronization and - * UTCTimeSynchronization requests shall be sent. If this - * property has a value of zero, then periodic time synchronization is - * disabled. If this property is present, it shall be writable. - */ -bool Device_Time_Sync_Interval_Set(uint32_t minutes) -{ - Interval_Minutes = minutes; - - return true; -} - -uint32_t Device_Time_Sync_Interval(void) -{ - return Interval_Minutes; -} - -/** - * Sets the time sync interval offset value. - * - * @param minutes - * This property, of type Unsigned, specifies the offset in - * minutes from the beginning of the period specified for time - * synchronization until the actual time synchronization requests - * are sent. The offset used shall be the value of Interval_Offset - * modulo the value of Time_Synchronization_Interval; - * e.g., if Interval_Offset has the value 31 and - * Time_Synchronization_Interval is 30, the offset used shall be 1. - * Interval_Offset shall have no effect if Align_Intervals is - * FALSE. If this property is present, it shall be writable. - */ -bool Device_Interval_Offset_Set(uint32_t minutes) -{ - Interval_Offset_Minutes = minutes; - - return true; -} - -uint32_t Device_Interval_Offset(void) -{ - return Interval_Offset_Minutes; -} -#endif - -/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or - BACNET_STATUS_ABORT for abort message */ -int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) -{ - int apdu_len = 0; /* return value */ - int len = 0; /* apdu len intermediate value */ - BACNET_BIT_STRING bit_string = { 0 }; - BACNET_CHARACTER_STRING char_string = { 0 }; - uint32_t i = 0; - BACNET_OBJECT_TYPE object_type = OBJECT_NONE; - uint32_t instance = 0; - uint32_t count = 0; - uint8_t *apdu = NULL; - struct object_functions *pObject = NULL; - bool found = false; - uint16_t apdu_max = 0; - - if ((rpdata == NULL) || (rpdata->application_data == NULL) || - (rpdata->application_data_len == 0)) { - return 0; - } - apdu = rpdata->application_data; - apdu_max = rpdata->application_data_len; - switch (rpdata->object_property) { - case PROP_OBJECT_IDENTIFIER: - apdu_len = encode_application_object_id( - &apdu[0], OBJECT_DEVICE, Object_Instance_Number); - break; - case PROP_OBJECT_NAME: - apdu_len = - encode_application_character_string(&apdu[0], &My_Object_Name); - break; - case PROP_OBJECT_TYPE: - apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); - break; - case PROP_DESCRIPTION: - characterstring_init_ansi(&char_string, Description); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_SYSTEM_STATUS: - apdu_len = encode_application_enumerated(&apdu[0], System_Status); - break; - case PROP_VENDOR_NAME: - characterstring_init_ansi(&char_string, Vendor_Name); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_VENDOR_IDENTIFIER: - apdu_len = encode_application_unsigned(&apdu[0], Vendor_Identifier); - break; - case PROP_MODEL_NAME: - characterstring_init_ansi(&char_string, Model_Name); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_FIRMWARE_REVISION: - characterstring_init_ansi(&char_string, BACnet_Version); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_APPLICATION_SOFTWARE_VERSION: - characterstring_init_ansi( - &char_string, Application_Software_Version); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_LOCATION: - characterstring_init_ansi(&char_string, Location); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; - case PROP_LOCAL_TIME: - Update_Current_Time(); - apdu_len = encode_application_time(&apdu[0], &Local_Time); - break; - case PROP_UTC_OFFSET: - Update_Current_Time(); - apdu_len = encode_application_signed(&apdu[0], UTC_Offset); - break; - case PROP_LOCAL_DATE: - Update_Current_Time(); - apdu_len = encode_application_date(&apdu[0], &Local_Date); - break; - case PROP_DAYLIGHT_SAVINGS_STATUS: - Update_Current_Time(); - apdu_len = - encode_application_boolean(&apdu[0], Daylight_Savings_Status); - break; - case PROP_PROTOCOL_VERSION: - apdu_len = encode_application_unsigned( - &apdu[0], Device_Protocol_Version()); - break; - case PROP_PROTOCOL_REVISION: - apdu_len = encode_application_unsigned( - &apdu[0], Device_Protocol_Revision()); - break; - case PROP_PROTOCOL_SERVICES_SUPPORTED: - /* Note: list of services that are executed, not initiated. */ - bitstring_init(&bit_string); - for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { - /* automatic lookup based on handlers set */ - bitstring_set_bit(&bit_string, (uint8_t)i, - apdu_service_supported((BACNET_SERVICES_SUPPORTED)i)); - } - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: - /* Note: this is the list of objects that can be in this device, - not a list of objects that this device can access */ - bitstring_init(&bit_string); - for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { - /* initialize all the object types to not-supported */ - bitstring_set_bit(&bit_string, (uint8_t)i, false); - } - /* set the object types with objects to supported */ - - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { - bitstring_set_bit(&bit_string, pObject->Object_Type, true); - } - pObject++; - } - apdu_len = encode_application_bitstring(&apdu[0], &bit_string); - break; - case PROP_OBJECT_LIST: - count = Device_Object_List_Count(); - /* Array element zero is the number of objects in the list */ - if (rpdata->array_index == 0) - apdu_len = encode_application_unsigned(&apdu[0], count); - /* if no index was specified, then try to encode the entire list */ - /* into one packet. Note that more than likely you will have */ - /* to return an error if the number of encoded objects exceeds */ - /* your maximum APDU size. */ - else if (rpdata->array_index == BACNET_ARRAY_ALL) { - for (i = 1; i <= count; i++) { - found = Device_Object_List_Identifier( - i, &object_type, &instance); - if (found) { - len = encode_application_object_id( - &apdu[apdu_len], object_type, instance); - apdu_len += len; - /* assume next one is the same size as this one */ - /* can we all fit into the APDU? Don't check for last - * entry */ - if ((i != count) && (apdu_len + len) >= apdu_max) { - /* Abort response */ - rpdata->error_code = - ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; - apdu_len = BACNET_STATUS_ABORT; - break; - } - } else { - /* error: internal error? */ - rpdata->error_class = ERROR_CLASS_SERVICES; - rpdata->error_code = ERROR_CODE_OTHER; - apdu_len = BACNET_STATUS_ERROR; - break; - } - } - } else { - found = Device_Object_List_Identifier( - rpdata->array_index, &object_type, &instance); - if (found) { - apdu_len = encode_application_object_id( - &apdu[0], object_type, instance); - } else { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; - } - } - break; - case PROP_MAX_APDU_LENGTH_ACCEPTED: - apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU); - break; - case PROP_SEGMENTATION_SUPPORTED: - apdu_len = encode_application_enumerated( - &apdu[0], Device_Segmentation_Supported()); - break; - case PROP_APDU_TIMEOUT: - apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout()); - break; - case PROP_NUMBER_OF_APDU_RETRIES: - apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); - break; - case PROP_DEVICE_ADDRESS_BINDING: - apdu_len = address_list_encode(&apdu[0], apdu_max); - break; - case PROP_DATABASE_REVISION: - apdu_len = encode_application_unsigned(&apdu[0], Database_Revision); - break; -#if defined(BACDL_MSTP) - case PROP_MAX_INFO_FRAMES: - apdu_len = - encode_application_unsigned(&apdu[0], dlmstp_max_info_frames()); - break; - case PROP_MAX_MASTER: - apdu_len = - encode_application_unsigned(&apdu[0], dlmstp_max_master()); - break; -#endif -#if defined(BACNET_TIME_MASTER) - case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: - apdu_len = handler_timesync_encode_recipients(&apdu[0], MAX_APDU); - if (apdu_len < 0) { - rpdata->error_code = - ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; - apdu_len = BACNET_STATUS_ABORT; - } - break; - case PROP_TIME_SYNCHRONIZATION_INTERVAL: - apdu_len = encode_application_unsigned( - &apdu[0], Device_Time_Sync_Interval()); - break; - case PROP_ALIGN_INTERVALS: - apdu_len = - encode_application_boolean(&apdu[0], Device_Align_Intervals()); - break; - case PROP_INTERVAL_OFFSET: - apdu_len = - encode_application_unsigned(&apdu[0], Device_Interval_Offset()); - break; -#endif - case PROP_ACTIVE_COV_SUBSCRIPTIONS: - apdu_len = handler_cov_encode_subscriptions(&apdu[0], apdu_max); - break; - default: - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - apdu_len = BACNET_STATUS_ERROR; - break; - } - /* only array properties can have array options */ - if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && - (rpdata->array_index != BACNET_ARRAY_ALL)) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } - - return apdu_len; -} - -/** Looks up the requested Object and Property, and encodes its Value in an - * APDU. - * @ingroup ObjIntf - * If the Object or Property can't be found, sets the error class and code. - * - * @param rpdata [in,out] Structure with the desired Object and Property info - * on entry, and APDU message on return. - * @return The length of the APDU on success, else BACNET_STATUS_ERROR - */ -int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) -{ - int apdu_len = BACNET_STATUS_ERROR; - struct object_functions *pObject = NULL; -#if (BACNET_PROTOCOL_REVISION >= 14) - struct special_property_list_t property_list; -#endif - - /* initialize the default return values */ - rpdata->error_class = ERROR_CLASS_OBJECT; - rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; - pObject = Device_Objects_Find_Functions(rpdata->object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(rpdata->object_instance)) { - if (pObject->Object_Read_Property) { -#if (BACNET_PROTOCOL_REVISION >= 14) - if ((int)rpdata->object_property == PROP_PROPERTY_LIST) { - Device_Objects_Property_List(rpdata->object_type, - rpdata->object_instance, &property_list); - apdu_len = property_list_encode(rpdata, - property_list.Required.pList, - property_list.Optional.pList, - property_list.Proprietary.pList); - } else -#endif - { - apdu_len = pObject->Object_Read_Property(rpdata); - } - } - } - } - - return apdu_len; -} - -/* returns true if successful */ -bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) -{ - bool status = false; /* return value */ - int len = 0; - BACNET_APPLICATION_DATA_VALUE value = { 0 }; - BACNET_OBJECT_TYPE object_type = OBJECT_NONE; - uint32_t object_instance = 0; - int temp; - - /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); - if (len < 0) { - /* error while decoding - a value larger than we can handle */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - return false; - } - if ((wp_data->object_property != PROP_OBJECT_LIST) && - (wp_data->array_index != BACNET_ARRAY_ALL)) { - /* only array properties can have array options */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } - /* FIXME: len < application_data_len: more data? */ - switch (wp_data->object_property) { - case PROP_OBJECT_IDENTIFIER: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_OBJECT_ID); - if (status) { - if ((value.type.Object_Id.type == OBJECT_DEVICE) && - (Device_Set_Object_Instance_Number( - value.type.Object_Id.instance))) { - /* FIXME: we could send an I-Am broadcast to let the world - * know */ - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; - case PROP_NUMBER_OF_APDU_RETRIES: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - /* FIXME: bounds check? */ - apdu_retries_set((uint8_t)value.type.Unsigned_Int); - } - break; - case PROP_APDU_TIMEOUT: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - /* FIXME: bounds check? */ - apdu_timeout_set((uint16_t)value.type.Unsigned_Int); - } - break; - case PROP_VENDOR_IDENTIFIER: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - /* FIXME: bounds check? */ - Device_Set_Vendor_Identifier((uint16_t)value.type.Unsigned_Int); - } - break; - case PROP_SYSTEM_STATUS: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_ENUMERATED); - if (status) { - temp = Device_Set_System_Status( - (BACNET_DEVICE_STATUS)value.type.Enumerated, false); - if (temp != 0) { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - if (temp == -1) { - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } else { - wp_data->error_code = - ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; - } - } - } - break; - case PROP_OBJECT_NAME: - status = write_property_string_valid(&wp_data, &value, - characterstring_capacity(&My_Object_Name)); - if (status) { - /* All the object names in a device must be unique */ - if (Device_Valid_Object_Name(&value.type.Character_String, - &object_type, &object_instance)) { - if ((object_type == wp_data->object_type) && - (object_instance == wp_data->object_instance)) { - /* writing same name to same object */ - status = true; - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; - } - } else { - Device_Set_Object_Name(&value.type.Character_String); - } - } - break; - case PROP_LOCATION: - status = write_property_empty_string_valid(&wp_data, &value, - MAX_DEV_LOC_LEN); - if (status) { - Device_Set_Location( - characterstring_value(&value.type.Character_String), - characterstring_length(&value.type.Character_String)); - } - break; - - case PROP_DESCRIPTION: - status = write_property_empty_string_valid(&wp_data, &value, - MAX_DEV_DESC_LEN); - if (status) { - Device_Set_Description( - characterstring_value(&value.type.Character_String), - characterstring_length(&value.type.Character_String)); - } - break; - case PROP_MODEL_NAME: - status = write_property_empty_string_valid(&wp_data, &value, - MAX_DEV_MOD_LEN); - if (status) { - Device_Set_Model_Name( - characterstring_value(&value.type.Character_String), - characterstring_length(&value.type.Character_String)); - } - break; -#if defined(BACNET_TIME_MASTER) - case PROP_TIME_SYNCHRONIZATION_INTERVAL: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - if (value.type.Unsigned_Int < 65535) { - minutes = value.type.Unsigned_Int; - Device_Time_Sync_Interval_Set(minutes); - status = true; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; - case PROP_ALIGN_INTERVALS: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_BOOLEAN); - if (status) { - Device_Align_Intervals_Set(value.type.Boolean); - status = true; - } - break; - case PROP_INTERVAL_OFFSET: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - if (value.type.Unsigned_Int < 65535) { - minutes = value.type.Unsigned_Int; - Device_Interval_Offset_Set(minutes); - status = true; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; -#else - case PROP_TIME_SYNCHRONIZATION_INTERVAL: - case PROP_ALIGN_INTERVALS: - case PROP_INTERVAL_OFFSET: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; -#endif - case PROP_UTC_OFFSET: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_SIGNED_INT); - if (status) { - if ((value.type.Signed_Int < (12 * 60)) && - (value.type.Signed_Int > (-12 * 60))) { - Device_UTC_Offset_Set(value.type.Signed_Int); - status = true; - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; -#if defined(BACDL_MSTP) - case PROP_MAX_INFO_FRAMES: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - if (value.type.Unsigned_Int <= 255) { - dlmstp_set_max_info_frames( - (uint8_t)value.type.Unsigned_Int); - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; - case PROP_MAX_MASTER: - status = write_property_type_valid(wp_data, &value, - BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - if ((value.type.Unsigned_Int > 0) && - (value.type.Unsigned_Int <= 127)) { - dlmstp_set_max_master((uint8_t)value.type.Unsigned_Int); - } else { - status = false; - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } - break; -#else - case PROP_MAX_INFO_FRAMES: - case PROP_MAX_MASTER: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; -#endif - case PROP_OBJECT_TYPE: - case PROP_VENDOR_NAME: - case PROP_FIRMWARE_REVISION: - case PROP_APPLICATION_SOFTWARE_VERSION: - case PROP_LOCAL_TIME: - case PROP_LOCAL_DATE: - case PROP_DAYLIGHT_SAVINGS_STATUS: - case PROP_PROTOCOL_VERSION: - case PROP_PROTOCOL_REVISION: - case PROP_PROTOCOL_SERVICES_SUPPORTED: - case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: - case PROP_OBJECT_LIST: - case PROP_MAX_APDU_LENGTH_ACCEPTED: - case PROP_SEGMENTATION_SUPPORTED: - case PROP_DEVICE_ADDRESS_BINDING: - case PROP_DATABASE_REVISION: - case PROP_ACTIVE_COV_SUBSCRIPTIONS: -#if defined(BACNET_TIME_MASTER) - case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: -#endif - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; - default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; - } - - return status; -} - -/** Looks up the requested Object and Property, and set the new Value in it, - * if allowed. - * If the Object or Property can't be found, sets the error class and code. - * @ingroup ObjIntf - * - * @param wp_data [in,out] Structure with the desired Object and Property info - * and new Value on entry, and APDU message on return. - * @return True on success, else False if there is an error. - */ -bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) -{ - bool status = false; /* Ever the pessamist! */ - struct object_functions *pObject = NULL; - - /* initialize the default return values */ - wp_data->error_class = ERROR_CLASS_OBJECT; - wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; - pObject = Device_Objects_Find_Functions(wp_data->object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(wp_data->object_instance)) { - if (pObject->Object_Write_Property) { -#if (BACNET_PROTOCOL_REVISION >= 14) - if (wp_data->object_property == PROP_PROPERTY_LIST) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else -#endif - { - status = pObject->Object_Write_Property(wp_data); - } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } - } else { - wp_data->error_class = ERROR_CLASS_OBJECT; - wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; - } - } else { - wp_data->error_class = ERROR_CLASS_OBJECT; - wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; - } - - return (status); -} - -/** Looks up the requested Object, and fills the Property Value list. - * If the Object or Property can't be found, returns false. - * @ingroup ObjHelpers - * @param [in] The object type to be looked up. - * @param [in] The object instance number to be looked up. - * @param [out] The value list - * @return True if the object instance supports this feature and value changed. - */ -bool Device_Encode_Value_List(BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - BACNET_PROPERTY_VALUE *value_list) -{ - bool status = false; /* Ever the pessamist! */ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(object_instance)) { - if (pObject->Object_Value_List) { - status = - pObject->Object_Value_List(object_instance, value_list); - } - } - } - - return (status); -} - -/** Checks the COV flag in the requested Object - * @ingroup ObjHelpers - * @param [in] The object type to be looked up. - * @param [in] The object instance to be looked up. - * @return True if the COV flag is set - */ -bool Device_COV(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) -{ - bool status = false; /* Ever the pessamist! */ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(object_instance)) { - if (pObject->Object_COV) { - status = pObject->Object_COV(object_instance); - } - } - } - - return (status); -} - -/** Clears the COV flag in the requested Object - * @ingroup ObjHelpers - * @param [in] The object type to be looked up. - * @param [in] The object instance to be looked up. - */ -void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) -{ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(object_instance)) { - if (pObject->Object_COV_Clear) { - pObject->Object_COV_Clear(object_instance); - } - } - } -} - -#if defined(INTRINSIC_REPORTING) -void Device_local_reporting(void) -{ - struct object_functions *pObject; - uint32_t objects_count; - uint32_t object_instance; - BACNET_OBJECT_TYPE object_type; - uint32_t idx; - - objects_count = Device_Object_List_Count(); - - /* loop for all objects */ - for (idx = 1; idx <= objects_count; idx++) { - Device_Object_List_Identifier(idx, &object_type, &object_instance); - - pObject = Device_Objects_Find_Functions(object_type); - if (pObject != NULL) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(object_instance)) { - if (pObject->Object_Intrinsic_Reporting) { - pObject->Object_Intrinsic_Reporting(object_instance); - } - } - } - } -} -#endif - -/** Looks up the requested Object to see if the functionality is supported. - * @ingroup ObjHelpers - * @param [in] The object type to be looked up. - * @return True if the object instance supports this feature. - */ -bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type) -{ - bool status = false; /* Ever the pessamist! */ - struct object_functions *pObject = NULL; - - pObject = Device_Objects_Find_Functions(object_type); - if (pObject != NULL) { - if (pObject->Object_Value_List) { - status = true; - } - } - - return (status); -} - -/** Initialize the Device Object. - Initialize the group of object helper functions for any supported Object. - Initialize each of the Device Object child Object instances. - * @ingroup ObjIntf - * @param object_table [in,out] array of structure with object functions. - * Each Child Object must provide some implementation of each of these - * functions in order to properly support the default handlers. - */ -void Device_Init(object_functions_t *object_table) -{ - struct object_functions *pObject = NULL; -#if defined(BAC_UCI) - const char *uciname; - struct uci_context *ctx; - fprintf(stderr, "Device_Init\n"); - ctx = ucix_init("bacnet_dev"); - if (!ctx) - fprintf(stderr, "Failed to load config file bacnet_dev\n"); - uciname = ucix_get_option(ctx, "bacnet_dev", "0", "Name"); - if (uciname != 0) { - characterstring_init_ansi(&My_Object_Name, uciname); - } else { -#endif /* defined(BAC_UCI) */ - characterstring_init_ansi(&My_Object_Name, "ESP32 SimpleServer"); -#if defined(BAC_UCI) - } - ucix_cleanup(ctx); -#endif /* defined(BAC_UCI) */ - - if (object_table) { - Object_Table = object_table; - } else { - Object_Table = &My_Object_Table[0]; - } - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Init) { - pObject->Object_Init(); - } - pObject++; - } -} - -bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ - RR_PROP_INFO *pInfo) -{ /* Where to put the response */ - bool status = false; /* return value */ - - switch (pRequest->object_property) { - case PROP_VT_CLASSES_SUPPORTED: - case PROP_ACTIVE_VT_SESSIONS: - case PROP_LIST_OF_SESSION_KEYS: - case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: - case PROP_MANUAL_SLAVE_ADDRESS_BINDING: - case PROP_SLAVE_ADDRESS_BINDING: - case PROP_RESTART_NOTIFICATION_RECIPIENTS: - case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: - pInfo->RequestTypes = RR_BY_POSITION; - pRequest->error_class = ERROR_CLASS_PROPERTY; - pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; - - case PROP_DEVICE_ADDRESS_BINDING: - pInfo->RequestTypes = RR_BY_POSITION; - pInfo->Handler = rr_address_list_encode; - status = true; - break; - - case PROP_ACTIVE_COV_SUBSCRIPTIONS: - pInfo->RequestTypes = RR_BY_POSITION; - pRequest->error_class = ERROR_CLASS_PROPERTY; - pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; - break; - default: - pRequest->error_class = ERROR_CLASS_SERVICES; - pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST; - break; - } - - return status; -} - -#ifdef BAC_ROUTING -/**************************************************************************** - ************* BACnet Routing Functionality (Optional) ********************** - **************************************************************************** - * The supporting functions are located in gw_device.c, except for those - * that need access to local data in this file. - ****************************************************************************/ - -/** Initialize the first of our array of Devices with the main Device's - * information, and then swap out some of the Device object functions and - * replace with ones appropriate for routing. - * @ingroup ObjIntf - * @param first_object_instance Set the first (gateway) Device to this - instance number. - */ -void Routing_Device_Init(uint32_t first_object_instance) -{ - struct object_functions *pDevObject = NULL; - - /* Initialize with our preset strings */ - Add_Routed_Device(first_object_instance, &My_Object_Name, Description); - - /* Now substitute our routed versions of the main object functions. */ - pDevObject = Object_Table; - pDevObject->Object_Index_To_Instance = Routed_Device_Index_To_Instance; - pDevObject->Object_Valid_Instance = - Routed_Device_Valid_Object_Instance_Number; - pDevObject->Object_Name = Routed_Device_Name; - pDevObject->Object_Read_Property = Routed_Device_Read_Property_Local; - pDevObject->Object_Write_Property = Routed_Device_Write_Property_Local; -} - -#endif /* BAC_ROUTING */ diff --git a/ports/esp32/src/device.h b/ports/esp32/src/device.h deleted file mode 100644 index 90c8e804..00000000 --- a/ports/esp32/src/device.h +++ /dev/null @@ -1,461 +0,0 @@ -/************************************************************************** -* -* Copyright (C) 2005 Steve Karg -* -* SPDX-License-Identifier: MIT -* -*********************************************************************/ - -/** @file device.h Defines functions for handling all BACnet objects belonging - * to a BACnet device, as well as Device-specific properties. */ - -#ifndef DEVICE_H -#define DEVICE_H - -#include -#include -#include "bacnet/bacdef.h" -#include "bacnet/bacenum.h" -#include "bacnet/wp.h" -#include "bacnet/rd.h" -#include "bacnet/rp.h" -#include "bacnet/rpm.h" -#include "bacnet/readrange.h" - -/** Called so a BACnet object can perform any necessary initialization. - * @ingroup ObjHelpers - */ -typedef void ( - *object_init_function) ( - void); - -/** Counts the number of objects of this type. - * @ingroup ObjHelpers - * @return Count of implemented objects of this type. - */ -typedef unsigned ( - *object_count_function) ( - void); - -/** Maps an object index position to its corresponding BACnet object instance number. - * @ingroup ObjHelpers - * @param index [in] The index of the object, in the array of objects of its type. - * @return The BACnet object instance number to be used in a BACNET_OBJECT_ID. - */ -typedef uint32_t( - *object_index_to_instance_function) - ( - unsigned index); - -/** Provides the BACnet Object_Name for a given object instance of this type. - * @ingroup ObjHelpers - * @param object_instance [in] The object instance number to be looked up. - * @param object_name [in,out] Pointer to a character_string structure that - * will hold a copy of the object name if this is a valid object_instance. - * @return True if the object_instance is valid and object_name has been - * filled with a copy of the Object's name. - */ -typedef bool( - *object_name_function) - ( - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - -/** Look in the table of objects of this type, and see if this is a valid - * instance number. - * @ingroup ObjHelpers - * @param [in] The object instance number to be looked up. - * @return True if the object instance refers to a valid object of this type. - */ -typedef bool( - *object_valid_instance_function) ( - uint32_t object_instance); - -/** Helper function to step through an array of objects and find either the - * first one or the next one of a given type. Used to step through an array - * of objects which is not necessarily contiguious for each type i.e. the - * index for the 'n'th object of a given type is not necessarily 'n'. - * @ingroup ObjHelpers - * @param [in] The index of the current object or a value of ~0 to indicate - * start at the beginning. - * @return The index of the next object of the required type or ~0 (all bits - * == 1) to indicate no more objects found. - */ -typedef unsigned ( - *object_iterate_function) ( - unsigned current_index); - -/** Look in the table of objects of this type, and get the COV Value List. - * @ingroup ObjHelpers - * @param [in] The object instance number to be looked up. - * @param [out] The value list - * @return True if the object instance supports this feature, and has changed. - */ -typedef bool( - *object_value_list_function) ( - uint32_t object_instance, - BACNET_PROPERTY_VALUE * value_list); - -/** Look in the table of objects for this instance to see if value changed. - * @ingroup ObjHelpers - * @param [in] The object instance number to be looked up. - * @return True if the object instance has changed. - */ -typedef bool( - *object_cov_function) ( - uint32_t object_instance); - -/** Look in the table of objects for this instance to clear the changed flag. - * @ingroup ObjHelpers - * @param [in] The object instance number to be looked up. - */ -typedef void ( - *object_cov_clear_function) ( - uint32_t object_instance); - -/** Intrinsic Reporting funcionality. - * @ingroup ObjHelpers - * @param [in] Object instance. - */ -typedef void ( - *object_intrinsic_reporting_function) ( - uint32_t object_instance); - - -/** Defines the group of object helper functions for any supported Object. - * @ingroup ObjHelpers - * Each Object must provide some implementation of each of these helpers - * in order to properly support the handlers. Eg, the ReadProperty handler - * handler_read_property() relies on the instance of Object_Read_Property - * for each Object type, or configure the function as NULL. - * In both appearance and operation, this group of functions acts like - * they are member functions of a C++ Object base class. - */ -typedef struct object_functions { - BACNET_OBJECT_TYPE Object_Type; - object_init_function Object_Init; - object_count_function Object_Count; - object_index_to_instance_function Object_Index_To_Instance; - object_valid_instance_function Object_Valid_Instance; - object_name_function Object_Name; - read_property_function Object_Read_Property; - write_property_function Object_Write_Property; - rpm_property_lists_function Object_RPM_List; - rr_info_function Object_RR_Info; - object_iterate_function Object_Iterator; - object_value_list_function Object_Value_List; - object_cov_function Object_COV; - object_cov_clear_function Object_COV_Clear; - object_intrinsic_reporting_function Object_Intrinsic_Reporting; -} object_functions_t; - -/* String Lengths - excluding any nul terminator */ -#define MAX_DEV_NAME_LEN 32 -#define MAX_DEV_LOC_LEN 64 -#define MAX_DEV_MOD_LEN 32 -#define MAX_DEV_VER_LEN 16 -#define MAX_DEV_DESC_LEN 64 - -/** Structure to define the Object Properties common to all Objects. */ -typedef struct commonBacObj_s { - - /** The BACnet type of this object (ie, what class is this object from?). - * This property, of type BACnetObjectType, indicates membership in a - * particular object type class. Each inherited class will be of one type. - */ - BACNET_OBJECT_TYPE mObject_Type; - - /** The instance number for this class instance. */ - uint32_t Object_Instance_Number; - - /** Object Name; must be unique. - * This property, of type CharacterString, shall represent a name for - * the object that is unique within the BACnet Device that maintains it. - */ - char Object_Name[MAX_DEV_NAME_LEN]; - -} COMMON_BAC_OBJECT; - - -/** Structure to define the Properties of Device Objects which distinguish - * one instance from another. - * This structure only defines fields for properties that are unique to - * a given Device object. The rest may be fixed in device.c or hard-coded - * into the read-property encoding. - * This may be useful for implementations which manage multiple Devices, - * eg, a Gateway. - */ -typedef struct devObj_s { - /** The BACnet Device Address for this device; ->len depends on DLL type. */ - BACNET_ADDRESS bacDevAddr; - - /** Structure for the Object Properties common to all Objects. */ - COMMON_BAC_OBJECT bacObj; - - /** Device Description. */ - char Description[MAX_DEV_DESC_LEN]; - - /** The upcounter that shows if the Device ID or object structure has changed. */ - uint32_t Database_Revision; -} DEVICE_OBJECT_DATA; - - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - - void Device_Init( - object_functions_t * object_table); - - bool Device_Reinitialize( - BACNET_REINITIALIZE_DEVICE_DATA * rd_data); - bool Device_Reinitialize_State_Set(BACNET_REINITIALIZED_STATE state); - BACNET_REINITIALIZED_STATE Device_Reinitialized_State( - void); - - rr_info_function Device_Objects_RR_Info( - BACNET_OBJECT_TYPE object_type); - - void Device_getCurrentDateTime( - BACNET_DATE_TIME * DateTime); - - int32_t Device_UTC_Offset(void); - void Device_UTC_Offset_Set(int16_t offset); - - bool Device_Daylight_Savings_Status(void); - bool Device_Align_Intervals(void); - bool Device_Align_Intervals_Set(bool flag); - uint32_t Device_Time_Sync_Interval(void); - bool Device_Time_Sync_Interval_Set(uint32_t value); - uint32_t Device_Interval_Offset(void); - bool Device_Interval_Offset_Set(uint32_t value); - - void Device_Property_Lists( - const int32_t **pRequired, - const int32_t **pOptional, - const int32_t **pProprietary); - void Device_Objects_Property_List( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - struct special_property_list_t *pPropertyList); - /* functions to support COV */ - bool Device_Encode_Value_List( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - BACNET_PROPERTY_VALUE * value_list); - bool Device_Value_List_Supported( - BACNET_OBJECT_TYPE object_type); - bool Device_COV( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance); - void Device_COV_Clear( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance); - - uint32_t Device_Object_Instance_Number( - void); - bool Device_Set_Object_Instance_Number( - uint32_t object_id); - bool Device_Valid_Object_Instance_Number( - uint32_t object_id); - unsigned Device_Object_List_Count( - void); - bool Device_Object_List_Identifier( - uint32_t array_index, - BACNET_OBJECT_TYPE *object_type, - uint32_t * instance); - - unsigned Device_Count( - void); - uint32_t Device_Index_To_Instance( - unsigned index); - - bool Device_Object_Name( - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - bool Device_Set_Object_Name( - const BACNET_CHARACTER_STRING * object_name); - /* Copy a child object name, given its ID. */ - bool Device_Object_Name_Copy( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - bool Device_Object_Name_ANSI_Init(const char * object_name); - char * Device_Object_Name_ANSI(void); - - BACNET_DEVICE_STATUS Device_System_Status( - void); - int Device_Set_System_Status( - BACNET_DEVICE_STATUS status, - bool local); - - const char *Device_Vendor_Name( - void); - - uint16_t Device_Vendor_Identifier( - void); - void Device_Set_Vendor_Identifier( - uint16_t vendor_id); - - const char *Device_Model_Name( - void); - bool Device_Set_Model_Name( - const char *name, - size_t length); - - const char *Device_Firmware_Revision( - void); - - const char *Device_Application_Software_Version( - void); - bool Device_Set_Application_Software_Version( - const char *name, - size_t length); - - const char *Device_Description( - void); - bool Device_Set_Description( - const char *name, - size_t length); - - const char *Device_Location( - void); - bool Device_Set_Location( - const char *name, - size_t length); - - /* some stack-centric constant values - no set methods */ - uint8_t Device_Protocol_Version( - void); - uint8_t Device_Protocol_Revision( - void); - BACNET_SEGMENTATION Device_Segmentation_Supported( - void); - - uint32_t Device_Database_Revision( - void); - void Device_Set_Database_Revision( - uint32_t revision); - void Device_Inc_Database_Revision( - void); - - bool Device_Valid_Object_Name( - const BACNET_CHARACTER_STRING * object_name, - BACNET_OBJECT_TYPE *object_type, - uint32_t * object_instance); - bool Device_Valid_Object_Id( - BACNET_OBJECT_TYPE object_type, - uint32_t object_instance); - - int Device_Read_Property( - BACNET_READ_PROPERTY_DATA * rpdata); - bool Device_Write_Property( - BACNET_WRITE_PROPERTY_DATA * wp_data); - - bool DeviceGetRRInfo( - BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */ - RR_PROP_INFO * pInfo); /* Where to put the information */ - - int Device_Read_Property_Local( - BACNET_READ_PROPERTY_DATA * rpdata); - bool Device_Write_Property_Local( - BACNET_WRITE_PROPERTY_DATA * wp_data); - -#if defined(INTRINSIC_REPORTING) - void Device_local_reporting( - void); -#endif - -/* Prototypes for Routing functionality in the Device Object. - * Enable by defining BAC_ROUTING in config.h and including gw_device.c - * in the build (lib/Makefile). - */ - void Routing_Device_Init( - uint32_t first_object_instance); - - uint16_t Add_Routed_Device( - uint32_t Object_Instance, - const BACNET_CHARACTER_STRING * Object_Name, - const char *Description); - DEVICE_OBJECT_DATA *Get_Routed_Device_Object( - int idx); - BACNET_ADDRESS *Get_Routed_Device_Address( - int idx); - - void routed_get_my_address( - BACNET_ADDRESS * my_address); - - bool Routed_Device_Address_Lookup( - int idx, - uint8_t address_len, - const uint8_t * mac_adress); - bool Routed_Device_GetNext( - const BACNET_ADDRESS * dest, - const int32_t *DNET_list, - int *cursor); - bool Routed_Device_Is_Valid_Network( - uint16_t dest_net, - const int32_t *DNET_list); - - uint32_t Routed_Device_Index_To_Instance( - unsigned index); - bool Routed_Device_Valid_Object_Instance_Number( - uint32_t object_id); - bool Routed_Device_Name( - uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - uint32_t Routed_Device_Object_Instance_Number( - void); - bool Routed_Device_Set_Object_Instance_Number( - uint32_t object_id); - bool Routed_Device_Set_Object_Name( - uint8_t encoding, - const char *value, - size_t length); - bool Routed_Device_Set_Description( - const char *name, - size_t length); - void Routed_Device_Inc_Database_Revision( - void); - int Routed_Device_Service_Approval( - BACNET_CONFIRMED_SERVICE service, - int service_argument, - uint8_t * apdu_buff, - uint8_t invoke_id); - - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -/** @defgroup ObjFrmwk Object Framework - * The modules in this section describe the BACnet-stack's framework for - * BACnet-defined Objects (Device, Analog Input, etc). There are two submodules - * to describe this arrangement: - * - The "object helper functions" which provide C++-like common functionality - * to all supported object types. - * - The interface between the implemented Objects and the BAC-stack services, - * specifically the handlers, which are mediated through function calls to - * the Device object. - */ -/** @defgroup ObjHelpers Object Helper Functions - * @ingroup ObjFrmwk - * This section describes the function templates for the helper functions that - * provide common object support. - */ -/** @defgroup ObjIntf Handler-to-Object Interface Functions - * @ingroup ObjFrmwk - * This section describes the fairly limited set of functions that link the - * BAC-stack handlers to the BACnet Object instances. All of these calls are - * situated in the Device Object, which "knows" how to reach its child Objects. - * - * Most of these calls have a common operation: - * -# Call Device_Objects_Find_Functions( for the desired Object_Type ) - * - Gets a pointer to the object_functions for this Type of Object. - * -# Call the Object's Object_Valid_Instance( for the desired object_instance ) - * to make sure there is such an instance. - * -# Call the Object helper function needed by the handler, - * eg Object_Read_Property() for the RP handler. - * - */ -#endif diff --git a/ports/esp32/src/dlenv.c b/ports/esp32/src/dlenv.c new file mode 100644 index 00000000..fd2abfa5 --- /dev/null +++ b/ports/esp32/src/dlenv.c @@ -0,0 +1,81 @@ +/** + * @file + * @brief BACnet MS/TP datalink environment setup for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#include "dlenv.h" + +#include "bacnet/datalink/dlmstp.h" +#include "rs485.h" + +volatile struct mstp_port_struct_t MSTP_Port; + +static struct dlmstp_user_data_t MSTP_User_Data; +static uint8_t Input_Buffer[DLMSTP_MPDU_MAX]; +static uint8_t Output_Buffer[DLMSTP_MPDU_MAX]; + +static struct dlmstp_rs485_driver RS485_Driver = { + .init = rs485_init, + .send = rs485_bytes_send, + .read = rs485_byte_available, + .transmitting = rs485_rts_enabled, + .baud_rate = rs485_baud_rate, + .baud_rate_set = rs485_baud_rate_set, + .silence_milliseconds = rs485_silence_milliseconds, + .silence_reset = rs485_silence_reset +}; + +/** + * @brief Initialize the BACnet MS/TP datalink environment + * @param mac_address local MS/TP MAC address + * @return true if the datalink initialized successfully + */ +bool m5_dlenv_init(uint8_t mac_address) +{ + RS485_Driver.init(); + + MSTP_Port.Nmax_info_frames = BACNET_MSTP_MAX_INFO_FRAMES; + MSTP_Port.Nmax_master = BACNET_MSTP_MAX_MASTER; + + MSTP_Port.InputBuffer = Input_Buffer; + MSTP_Port.InputBufferSize = sizeof(Input_Buffer); + MSTP_Port.OutputBuffer = Output_Buffer; + MSTP_Port.OutputBufferSize = sizeof(Output_Buffer); + + MSTP_Port.ZeroConfigEnabled = false; + MSTP_Port.SlaveNodeEnabled = false; + MSTP_Port.CheckAutoBaud = false; + + MSTP_Zero_Config_UUID_Init((struct mstp_port_struct_t *)&MSTP_Port); + + MSTP_User_Data.RS485_Driver = &RS485_Driver; + MSTP_Port.UserData = &MSTP_User_Data; + + if (!dlmstp_init((char *)&MSTP_Port)) { + return false; + } + + dlmstp_set_mac_address(mac_address); + dlmstp_set_baud_rate(RS485_BAUD_RATE); + dlmstp_set_max_master(BACNET_MSTP_MAX_MASTER); + dlmstp_set_max_info_frames(BACNET_MSTP_MAX_INFO_FRAMES); + + return true; +} + +/** + * @brief Run any periodic MS/TP maintenance hooks + * @param seconds elapsed seconds since the last call + */ +void dlenv_maintenance_timer(uint16_t seconds) +{ + (void)seconds; +} + +/** + * @brief Shut down the MS/TP datalink environment + */ +void dlenv_cleanup(void) +{ +} diff --git a/ports/esp32/src/dlenv.h b/ports/esp32/src/dlenv.h new file mode 100644 index 00000000..f65d5426 --- /dev/null +++ b/ports/esp32/src/dlenv.h @@ -0,0 +1,48 @@ +/** + * @file + * @brief BACnet MS/TP datalink environment declarations for the PlatformIO + * ESP32 port + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_DLENV_H +#define M5STAMPLC_DLENV_H + +#include +#include + +#include "bacnet/bacdef.h" +#include "bacnet/datalink/dlenv.h" +#include "bacnet/datalink/dlmstp.h" +#include "bacnet/datalink/mstp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BACNET_MSTP_MAX_MASTER 127 +#define BACNET_MSTP_MAX_INFO_FRAMES 1 + +/** + * @brief Initialize the BACnet MS/TP datalink environment + * @param mac_address local MS/TP MAC address + * @return true if initialization succeeded + */ +bool m5_dlenv_init(uint8_t mac_address); + +/** + * @brief Run periodic MS/TP maintenance work + * @param seconds elapsed seconds since the last call + */ +void dlenv_maintenance_timer(uint16_t seconds); + +/** + * @brief Shut down the MS/TP datalink environment + */ +void dlenv_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/main.c b/ports/esp32/src/main.c deleted file mode 100644 index 0c2f4e5a..00000000 --- a/ports/esp32/src/main.c +++ /dev/null @@ -1,218 +0,0 @@ -// -// Copyleft F.Chaxel 2017 -// - -#include "bacnet/config.h" -#include "bacnet/basic/tsm/tsm.h" -#include "bacnet/basic/services.h" - -#include "bacnet/basic/services.h" -#include "bacnet/datalink/datalink.h" -#include "bacnet/dcc.h" -#include "bacnet/basic/tsm/tsm.h" -// conflict filename address.h with another file in default include paths -#include "../lib/stack/address.h" -#include "bacnet/datalink/bip.h" - -#include "bacnet/basic/object/device.h" -#include "bacnet/basic/object/ai.h" -#include "bacnet/basic/object/bo.h" - -#include "esp_log.h" -#include "esp_wifi.h" -#include "esp_event_loop.h" -#include "nvs_flash.h" - -#include "driver/gpio.h" - -#include "lwip/sockets.h" -#include "lwip/netdb.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" - -// hidden function not in any .h files -extern uint8_t temprature_sens_read(); -extern uint32_t hall_sens_read(); - -// Wifi params -wifi_config_t wifi_config = { - .sta = { - .ssid = "myWifi", - .password = "myPass", - }, -}; - -// GPIO 5 has a Led on Sparkfun ESP32 board -#define BACNET_LED 5 - -uint8_t Handler_Transmit_Buffer[MAX_PDU] = { 0 }; - -/** Static receive buffer, initialized with zeros by the C Library Startup Code. */ - -uint8_t Rx_Buf[MAX_MPDU + 16 /* Add a little safety margin to the buffer, - * so that in the rare case, the message - * would be filled up to MAX_MPDU and some - * decoding functions would overrun, these - * decoding functions will just end up in - * a safe field of static zeros. */] = { 0 }; - -EventGroupHandle_t wifi_event_group; -const static int CONNECTED_BIT = BIT0; - -/* BACnet handler, stack init, IAm */ -void StartBACnet() -{ - /* we need to handle who-is to support dynamic device binding */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); - - /* set the handler for all the services we don't implement */ - /* It is required to send the proper reject message... */ - apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); - /* Set the handlers for any confirmed services that we support. */ - /* We must implement read property - it's required! */ - apdu_set_confirmed_handler( - SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); - apdu_set_confirmed_handler( - SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple); - - apdu_set_confirmed_handler( - SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property); - apdu_set_confirmed_handler( - SERVICE_CONFIRMED_SUBSCRIBE_COV, handler_cov_subscribe); - - address_init(); - bip_init(NULL); - Send_I_Am(&Handler_Transmit_Buffer[0]); -} - -/* wifi events handler : start & stop bacnet with an event */ -esp_err_t wifi_event_handler(void *ctx, system_event_t *event) -{ - switch (event->event_id) { - case SYSTEM_EVENT_STA_START: - esp_wifi_connect(); - break; - case SYSTEM_EVENT_STA_CONNECTED: - break; - case SYSTEM_EVENT_STA_GOT_IP: - if (xEventGroupGetBits(wifi_event_group) != CONNECTED_BIT) { - xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); - StartBACnet(); - } - break; - case SYSTEM_EVENT_STA_DISCONNECTED: - /* This is a workaround as ESP32 WiFi libs don't currently - auto-reassociate. */ - esp_wifi_connect(); - xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); - bip_cleanup(); - break; - default: - break; - } - return ESP_OK; -} - -/* tcpip & wifi station start */ - -void wifi_init_station(void) -{ - tcpip_adapter_init(); - wifi_event_group = xEventGroupCreate(); - esp_event_loop_init(wifi_event_handler, NULL); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_wifi_init(&cfg); - - esp_wifi_set_storage(WIFI_STORAGE_RAM); - esp_wifi_set_mode(WIFI_MODE_STA); - - esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); - - esp_wifi_start(); -} - -/* setup gpio & nv flash, call wifi init code */ -void setup() -{ - gpio_pad_select_gpio(BACNET_LED); - gpio_set_direction(BACNET_LED, GPIO_MODE_OUTPUT); - - gpio_set_level(BACNET_LED, 0); - - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { - nvs_flash_erase(); - ret = nvs_flash_init(); - } - wifi_init_station(); -} - -/* Bacnet Task */ -void BACnetTask(void *pvParameters) -{ - uint16_t pdu_len = 0; - BACNET_ADDRESS src = { 0 }; - unsigned timeout = 1; - - // Init Bacnet objets dictionnary - Device_Init(NULL); - Device_Set_Object_Instance_Number(12); - - setup(); - - uint32_t tickcount = xTaskGetTickCount(); - - for (;;) { - vTaskDelay( - 10 / portTICK_PERIOD_MS); // could be remove to speed the code - - // do nothing if not connected to wifi - xEventGroupWaitBits( - wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); - { - uint32_t newtick = xTaskGetTickCount(); - - // one second elapse at least (maybe much more if Wifi was - // deconnected for a long) - if ((newtick < tickcount) || - ((newtick - tickcount) >= configTICK_RATE_HZ)) { - tickcount = newtick; - dcc_timer_seconds(1); - bvlc_maintenance_timer(1); - handler_cov_timer_seconds(1); - tsm_timer_milliseconds(1000); - - // Read analog values from internal sensors - Analog_Input_Present_Value_Set(0, temprature_sens_read()); - Analog_Input_Present_Value_Set(1, hall_sens_read()); - } - - pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); - if (pdu_len) { - npdu_handler(&src, &Rx_Buf[0], pdu_len); - - if (Binary_Output_Present_Value(0) == BINARY_ACTIVE) - gpio_set_level(BACNET_LED, 1); - else - gpio_set_level(BACNET_LED, 0); - } - - handler_cov_task(); - } - } -} -/* Entry point */ -void app_main() -{ - // Cannot run BACnet code here, the default stack size is to small : 4096 - // byte - xTaskCreate(BACnetTask, /* Function to implement the task */ - "BACnetTask", /* Name of the task */ - 10000, /* Stack size in words */ - NULL, /* Task input parameter */ - 20, /* Priority of the task */ - NULL); /* Task handle. */ -} diff --git a/ports/esp32/src/main.cpp b/ports/esp32/src/main.cpp new file mode 100644 index 00000000..3ea07585 --- /dev/null +++ b/ports/esp32/src/main.cpp @@ -0,0 +1,57 @@ +/** + * @file + * @brief BACnet MS/TP application entry point for M5StamPLC + * @author Kato Gangstad + */ + +#include +#include + +#ifndef PLC_INPUT_ACTIVE_LOW +#define PLC_INPUT_ACTIVE_LOW 0 +#endif + +extern "C" { +#include "bacnet_app.h" +} + +/** + * @brief Initialize the MS/TP application and PLC hardware + */ +void setup(void) +{ + Serial.begin(115200); + delay(100); + Serial.println("[BOOT] Mode: BACnet MS/TP"); + Serial.println("[BOOT] Init M5StamPLC..."); + M5StamPLC.begin(); + Serial.println("[BOOT] Init BACnet app..."); + bacnet_app_init(); + Serial.println("[BOOT] BACnet MS/TP ready"); +} + +/** + * @brief Run the main MS/TP application loop + */ +void loop(void) +{ + M5StamPLC.update(); + + for (uint8_t i = 0; i < bacnet_app_input_count(); i++) { + bool state = M5StamPLC.readPlcInput(i); +#if PLC_INPUT_ACTIVE_LOW + state = !state; +#endif + bacnet_app_input_set(i, state); + } + + for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) { + M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i)); + } + + bacnet_app_temperature_set(M5StamPLC.getTemp()); + bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f); + + bacnet_app_tick(); + delay(1); +} diff --git a/ports/esp32/src/main_bip.cpp b/ports/esp32/src/main_bip.cpp new file mode 100644 index 00000000..b6adc726 --- /dev/null +++ b/ports/esp32/src/main_bip.cpp @@ -0,0 +1,152 @@ +/** + * @file + * @brief BACnet/IP application entry point for ESP32 PlatformIO environments + * @author Kato Gangstad + */ + +#include +#include +#if !defined(USE_M5STAMPLC_IO) || (USE_M5STAMPLC_IO) +#include +#endif +#include +#if defined(USE_ETH_INTERFACE) && (USE_ETH_INTERFACE) +#include +#endif + +#ifndef PLC_INPUT_ACTIVE_LOW +#define PLC_INPUT_ACTIVE_LOW 0 +#endif + +#ifndef USE_M5STAMPLC_IO +#define USE_M5STAMPLC_IO 1 +#endif + +#ifndef USE_ETH_INTERFACE +#define USE_ETH_INTERFACE 0 +#endif + +#ifndef WIFI_SSID +#define WIFI_SSID "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef BACNET_IP_PORT +#define BACNET_IP_PORT 47808 +#endif + +extern "C" { +#include "bacnet_app.h" +} + +/** + * @brief Bring up the selected WiFi or Ethernet interface + */ +static void wifi_connect(void) +{ +#if USE_ETH_INTERFACE + Serial.println("[ETH] Starting Ethernet..."); + if (!ETH.begin()) { + Serial.println("[ETH] ETH.begin() failed"); + return; + } + + unsigned long start = millis(); + while (!ETH.linkUp() && ((millis() - start) < 15000UL)) { + delay(200); + Serial.print('.'); + } + Serial.println(); + + if (ETH.linkUp()) { + Serial.printf( + "[ETH] Link up. IP: %s\n", ETH.localIP().toString().c_str()); + } else { + Serial.println("[ETH] Link timeout"); + } +#else + if (strlen(WIFI_SSID) == 0) { + Serial.println("[WIFI] SSID not configured; skipping WiFi connect"); + return; + } + + Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID); + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + + unsigned long start = millis(); + while ((WiFi.status() != WL_CONNECTED) && ((millis() - start) < 15000UL)) { + delay(200); + Serial.print('.'); + } + + Serial.println(); + if (WiFi.status() == WL_CONNECTED) { + Serial.printf( + "[WIFI] Connected. IP: %s\n", WiFi.localIP().toString().c_str()); + } else { + Serial.printf( + "[WIFI] Connect timeout. status=%d\n", (int)WiFi.status()); + } +#endif +} + +/** + * @brief Initialize the BACnet/IP application and selected board profile + */ +void setup(void) +{ + Serial.begin(115200); + delay(100); + Serial.println("[BOOT] Mode: BACnet/IP"); + Serial.printf("[BOOT] BACnet UDP Port: %u\n", (unsigned)BACNET_IP_PORT); +#if USE_ETH_INTERFACE + Serial.println("[BOOT] Network: Ethernet"); +#else + Serial.println("[BOOT] Network: WiFi"); +#endif +#if USE_M5STAMPLC_IO + Serial.println("[BOOT] Board profile: M5StamPLC I/O enabled"); + M5StamPLC.begin(); +#else + Serial.println("[BOOT] Board profile: Generic/ESP32-PoE (M5 I/O disabled)"); +#endif + wifi_connect(); + Serial.println("[BOOT] Init BACnet app..."); + bacnet_app_init(); + Serial.println("[BOOT] BACnet/IP ready"); +} + +/** + * @brief Run the main BACnet/IP application loop + */ +void loop(void) +{ +#if USE_M5STAMPLC_IO + M5StamPLC.update(); + + for (uint8_t i = 0; i < bacnet_app_input_count(); i++) { + bool state = M5StamPLC.readPlcInput(i); +#if PLC_INPUT_ACTIVE_LOW + state = !state; +#endif + bacnet_app_input_set(i, state); + } + + for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) { + M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i)); + } + + bacnet_app_temperature_set(M5StamPLC.getTemp()); + bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f); +#else + bacnet_app_temperature_set(0.0f); + bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f); +#endif + + bacnet_app_tick(); + delay(1); +} diff --git a/ports/esp32/src/main_gateway_router.cpp b/ports/esp32/src/main_gateway_router.cpp new file mode 100644 index 00000000..0123cd34 --- /dev/null +++ b/ports/esp32/src/main_gateway_router.cpp @@ -0,0 +1,332 @@ +/** + * @file + * @brief BACnet router entry point bridging BACnet/IP and BACnet MS/TP + * @author Kato Gangstad + */ + +#include +#include +#include +#include + +extern "C" { +#include "bacnet_app.h" +#include "bip.h" +#include "dlenv.h" +#include "bacnet/bacdcode.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/datalink/dlmstp.h" +#include "bacnet/basic/npdu/h_npdu.h" +#include "bacnet/basic/tsm/tsm.h" +} + +#ifndef WIFI_SSID +#define WIFI_SSID "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef BACNET_IP_PORT +#define BACNET_IP_PORT 47808 +#endif + +#ifndef ROUTER_BIP_NET +#define ROUTER_BIP_NET 1 +#endif + +#ifndef ROUTER_MSTP_NET +#define ROUTER_MSTP_NET 200 +#endif + +#ifndef ROUTER_MSTP_MAC +#define ROUTER_MSTP_MAC 2 +#endif + +#ifndef PLC_INPUT_ACTIVE_LOW +#define PLC_INPUT_ACTIVE_LOW 0 +#endif + +#ifndef GATEWAY_MAX +#define GATEWAY_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +static uint8_t BipRxBuffer[BIP_MPDU_MAX]; +static uint8_t MstpRxBuffer[DLMSTP_MPDU_MAX]; +static uint8_t TxBuffer[GATEWAY_MAX(BIP_MPDU_MAX, DLMSTP_MPDU_MAX)]; + +/** + * @brief Connect the BACnet/IP side of the router to WiFi + */ +static void wifi_connect(void) +{ + if (strlen(WIFI_SSID) == 0) { + Serial.println("[WIFI] SSID not configured; skipping WiFi connect"); + return; + } + + Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID); + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + + unsigned long start = millis(); + while ((WiFi.status() != WL_CONNECTED) && ((millis() - start) < 15000UL)) { + delay(200); + Serial.print('.'); + } + Serial.println(); + + if (WiFi.status() == WL_CONNECTED) { + Serial.printf( + "[WIFI] Connected. IP: %s\n", WiFi.localIP().toString().c_str()); + } else { + Serial.printf( + "[WIFI] Connect timeout. status=%d\n", (int)WiFi.status()); + } +} + +/** + * @brief Send a BACnet router network message on the MS/TP segment + * @param type network layer message type + * @param dnet destination network number, or negative when omitted + * @return number of bytes sent, or a negative value on error + */ +static int +send_router_message_on_mstp(BACNET_NETWORK_MESSAGE_TYPE type, int dnet) +{ + BACNET_NPDU_DATA npdu_data = { 0 }; + BACNET_ADDRESS dest = { 0 }; + int pdu_len = 0; + + dest.mac_len = 0; + dest.net = BACNET_BROADCAST_NETWORK; + dest.len = 0; + + npdu_encode_npdu_network(&npdu_data, type, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(TxBuffer, &dest, NULL, &npdu_data); + if (dnet >= 0) { + pdu_len += encode_unsigned16(&TxBuffer[pdu_len], (uint16_t)dnet); + } + + return dlmstp_send_pdu(&dest, &npdu_data, TxBuffer, (unsigned)pdu_len); +} + +/** + * @brief Send a BACnet router network message on the BACnet/IP segment + * @param type network layer message type + * @param dnet destination network number, or negative when omitted + * @return number of bytes sent, or a negative value on error + */ +static int +send_router_message_on_bip(BACNET_NETWORK_MESSAGE_TYPE type, int dnet) +{ + BACNET_NPDU_DATA npdu_data = { 0 }; + BACNET_ADDRESS dest = { 0 }; + int pdu_len = 0; + + bip_get_broadcast_address(&dest); + + npdu_encode_npdu_network(&npdu_data, type, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(TxBuffer, &dest, NULL, &npdu_data); + if (dnet >= 0) { + pdu_len += encode_unsigned16(&TxBuffer[pdu_len], (uint16_t)dnet); + } + + return bip_send_pdu(&dest, &npdu_data, TxBuffer, (unsigned)pdu_len); +} + +/** + * @brief Check whether a received NPDU should be processed by the local device + * @param dest decoded destination address + * @return true if the packet targets the local device or broadcast + */ +static bool should_process_locally(const BACNET_ADDRESS *dest) +{ + return (dest->net == 0U) || (dest->net == BACNET_BROADCAST_NETWORK); +} + +/** + * @brief Route an NPDU between BACnet/IP and MS/TP segments + * @param from_bip true when the frame arrived from BACnet/IP + * @param src decoded source address + * @param pdu NPDU buffer to route + * @param pdu_len NPDU length in bytes + */ +static void +forward_pdu(bool from_bip, BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_len) +{ + BACNET_ADDRESS dest = { 0 }; + BACNET_ADDRESS routed_src = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + int apdu_offset = bacnet_npdu_decode(pdu, pdu_len, &dest, src, &npdu_data); + if (apdu_offset <= 0) { + return; + } + + const uint16_t src_net = from_bip ? ROUTER_BIP_NET : ROUTER_MSTP_NET; + const uint16_t dst_net = from_bip ? ROUTER_MSTP_NET : ROUTER_BIP_NET; + + if (npdu_data.network_layer_message) { + if (npdu_data.network_message_type == + NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK) { + uint16_t requested = 0; + bool has_dnet = (pdu_len - apdu_offset) >= 2; + if (has_dnet) { + (void)decode_unsigned16(&pdu[apdu_offset], &requested); + } + if (!has_dnet || (requested == dst_net)) { + if (from_bip) { + send_router_message_on_bip( + NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, dst_net); + } else { + send_router_message_on_mstp( + NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, dst_net); + } + } + } + return; + } + + if ((dest.net != 0U) && (dest.net != BACNET_BROADCAST_NETWORK) && + (dest.net != dst_net)) { + return; + } + + if (npdu_data.hop_count > 0) { + npdu_data.hop_count--; + if (npdu_data.hop_count == 0) { + return; + } + } + + if (src->net != 0U) { + routed_src = *src; + } else { + memset(&routed_src, 0, sizeof(routed_src)); + routed_src.net = src_net; + routed_src.len = src->mac_len; + for (uint8_t i = 0; i < src->mac_len && i < MAX_MAC_LEN; i++) { + routed_src.adr[i] = src->mac[i]; + } + } + + BACNET_ADDRESS out_dest = { 0 }; + if (dest.net == BACNET_BROADCAST_NETWORK || + ((dest.net == dst_net) && (dest.len == 0))) { + out_dest.mac_len = 0; + out_dest.net = BACNET_BROADCAST_NETWORK; + out_dest.len = 0; + } else if ((dest.net == dst_net) && (dest.len > 0)) { + out_dest.mac_len = dest.len; + for (uint8_t i = 0; i < dest.len && i < MAX_MAC_LEN; i++) { + out_dest.mac[i] = dest.adr[i]; + } + out_dest.net = 0; + out_dest.len = 0; + } else { + out_dest = dest; + } + + int out_len = npdu_encode_pdu(TxBuffer, &out_dest, &routed_src, &npdu_data); + if (out_len <= 0) { + return; + } + + const uint16_t apdu_len = (uint16_t)(pdu_len - apdu_offset); + if ((out_len + (int)apdu_len) > (int)sizeof(TxBuffer)) { + return; + } + memcpy(&TxBuffer[out_len], &pdu[apdu_offset], apdu_len); + out_len += apdu_len; + + if (from_bip) { + (void)dlmstp_send_pdu( + &out_dest, &npdu_data, TxBuffer, (unsigned)out_len); + } else { + (void)bip_send_pdu(&out_dest, &npdu_data, TxBuffer, (unsigned)out_len); + } +} + +/** + * @brief Initialize the BACnet router and local device services + */ +void setup(void) +{ + Serial.begin(115200); + delay(100); + Serial.println("[BOOT] Mode: BACnet Gateway (Router + Local Device)"); + Serial.printf( + "[GATEWAY] BIP NET=%u MSTP NET=%u MSTP MAC=%u\n", + (unsigned)ROUTER_BIP_NET, (unsigned)ROUTER_MSTP_NET, + (unsigned)ROUTER_MSTP_MAC); + + M5StamPLC.begin(); + wifi_connect(); + + /* Local device + objects run on B/IP side (same logic as existing M5 app). + */ + bacnet_app_init(); + + if (!m5_dlenv_init((uint8_t)ROUTER_MSTP_MAC)) { + Serial.println("[GATEWAY] ERROR: MSTP init failed"); + for (;;) { + delay(1000); + } + } + + (void)send_router_message_on_bip( + NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, ROUTER_MSTP_NET); + (void)send_router_message_on_mstp( + NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, ROUTER_BIP_NET); + + Serial.println("[GATEWAY] Ready"); +} + +/** + * @brief Run the BACnet router main loop + */ +void loop(void) +{ + BACNET_ADDRESS src = { 0 }; + BACNET_ADDRESS dest = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + uint16_t pdu_len = 0; + + M5StamPLC.update(); + + for (uint8_t i = 0; i < bacnet_app_input_count(); i++) { + bool state = M5StamPLC.readPlcInput(i); +#if PLC_INPUT_ACTIVE_LOW + state = !state; +#endif + bacnet_app_input_set(i, state); + } + + for (uint8_t i = 0; i < bacnet_app_relay_count(); i++) { + M5StamPLC.writePlcRelay(i, bacnet_app_relay_get(i)); + } + + bacnet_app_temperature_set(M5StamPLC.getTemp()); + bacnet_app_free_heap_kb_set((float)ESP.getFreeHeap() / 1024.0f); + + pdu_len = bip_receive(&src, BipRxBuffer, sizeof(BipRxBuffer), 0); + if (pdu_len > 0) { + int apdu_offset = + bacnet_npdu_decode(BipRxBuffer, pdu_len, &dest, &src, &npdu_data); + if ((apdu_offset > 0) && should_process_locally(&dest)) { + npdu_handler(&src, BipRxBuffer, pdu_len); + } + forward_pdu(true, &src, BipRxBuffer, pdu_len); + } + + pdu_len = dlmstp_receive(&src, MstpRxBuffer, sizeof(MstpRxBuffer), 0); + if (pdu_len > 0) { + forward_pdu(false, &src, MstpRxBuffer, pdu_len); + } + + tsm_timer_milliseconds(1); + + delay(1); +} diff --git a/ports/esp32/src/mstimer_init.c b/ports/esp32/src/mstimer_init.c new file mode 100644 index 00000000..a10269dc --- /dev/null +++ b/ports/esp32/src/mstimer_init.c @@ -0,0 +1,26 @@ +/** + * @file + * @brief BACnet millisecond timer hooks for the PlatformIO ESP32 port + * @author Kato Gangstad + */ + +#include "mstimer_init.h" + +#include "esp_timer.h" + +/** + * @brief Initialize the system timer services used by BACnet + */ +void systimer_init(void) +{ + /* esp_timer is available after runtime startup on ESP32. */ +} + +/** + * @brief Get the current millisecond tick count + * @return milliseconds since startup + */ +unsigned long mstimer_now(void) +{ + return (unsigned long)(esp_timer_get_time() / 1000ULL); +} diff --git a/ports/esp32/src/mstimer_init.h b/ports/esp32/src/mstimer_init.h new file mode 100644 index 00000000..55c5f9df --- /dev/null +++ b/ports/esp32/src/mstimer_init.h @@ -0,0 +1,32 @@ +/** + * @file + * @brief BACnet millisecond timer hook declarations for the PlatformIO ESP32 + * port + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_MSTIMER_INIT_H +#define M5STAMPLC_MSTIMER_INIT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the system timer services used by BACnet + */ +void systimer_init(void); + +/** + * @brief Get the current millisecond tick count + * @return milliseconds since startup + */ +unsigned long mstimer_now(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/rs485.c b/ports/esp32/src/rs485.c new file mode 100644 index 00000000..7e89c81a --- /dev/null +++ b/ports/esp32/src/rs485.c @@ -0,0 +1,184 @@ +/** + * @file + * @brief RS485 UART driver used by BACnet MS/TP on ESP32 + * @author Kato Gangstad + */ + +#include "rs485.h" + +#include +#include + +/* UART_SCLK_DEFAULT was introduced in ESP-IDF v5.0. + Fall back to UART_SCLK_APB for older IDF/Arduino-ESP32 toolchains. */ +#ifndef UART_SCLK_DEFAULT +#define UART_SCLK_DEFAULT UART_SCLK_APB +#endif + +#include "bacnet/basic/sys/mstimer.h" + +static bool Rs485_RTS_Enabled; +static uint32_t Rs485_Baud_Rate = RS485_BAUD_RATE; +static volatile uint32_t Rs485_Bytes_Tx; +static volatile uint32_t Rs485_Bytes_Rx; +static struct mstimer Silence_Timer; + +/** + * @brief Initialize the RS485 UART peripheral for BACnet MS/TP + */ +void rs485_init(void) +{ + uart_config_t cfg = { .baud_rate = (int)Rs485_Baud_Rate, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT }; + + (void)uart_driver_delete(RS485_UART_NUM); + (void)uart_param_config(RS485_UART_NUM, &cfg); + (void)uart_set_pin( + RS485_UART_NUM, RS485_TX_PIN, RS485_RX_PIN, RS485_DIR_PIN, + UART_PIN_NO_CHANGE); + (void)uart_driver_install(RS485_UART_NUM, 1024, 0, 0, NULL, 0); + (void)uart_set_mode(RS485_UART_NUM, UART_MODE_RS485_HALF_DUPLEX); + + Rs485_RTS_Enabled = false; + Rs485_Bytes_Tx = 0; + Rs485_Bytes_Rx = 0; + rs485_silence_reset(); +} + +/** + * @brief Update the cached RS485 transmit-enable state + * @param enable true when transmit is active + */ +void rs485_rts_enable(bool enable) +{ + /* Hardware RS485 half-duplex mode drives DIR. Keep state for stack queries. + */ + Rs485_RTS_Enabled = enable; +} + +/** + * @brief Check whether the RS485 transmit-enable state is active + * @return true if transmit is active + */ +bool rs485_rts_enabled(void) +{ + return Rs485_RTS_Enabled; +} + +/** + * @brief Read one byte from the RS485 receive FIFO when available + * @param data_register destination for the received byte, or NULL to probe only + * @return true if at least one byte is available + */ +bool rs485_byte_available(uint8_t *data_register) +{ + size_t buffered = 0; + (void)uart_get_buffered_data_len(RS485_UART_NUM, &buffered); + if (buffered == 0U) { + return false; + } + if (!data_register) { + return true; + } + + if (uart_read_bytes(RS485_UART_NUM, data_register, 1, 0) == 1) { + Rs485_Bytes_Rx++; + rs485_silence_reset(); + return true; + } + + return false; +} + +/** + * @brief Report whether an RS485 receive error is pending + * @return true if a receive error is detected + */ +bool rs485_receive_error(void) +{ + /* In Arduino-ESP32 builds, low-level UART error queues are not always + * exposed. */ + return false; +} + +/** + * @brief Send a block of RS485 bytes + * @param buffer transmit buffer + * @param nbytes number of bytes to send + */ +void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes) +{ + if (!buffer || (nbytes == 0U)) { + return; + } + + rs485_rts_enable(true); + (void)uart_write_bytes(RS485_UART_NUM, (const char *)buffer, nbytes); + (void)uart_wait_tx_done(RS485_UART_NUM, pdMS_TO_TICKS(20)); + Rs485_Bytes_Tx += nbytes; + rs485_rts_enable(false); + rs485_silence_reset(); +} + +/** + * @brief Get the configured RS485 baud rate + * @return baud rate in bits per second + */ +uint32_t rs485_baud_rate(void) +{ + return Rs485_Baud_Rate; +} + +/** + * @brief Set the RS485 baud rate + * @param baud baud rate in bits per second + * @return true if the baud rate was applied + */ +bool rs485_baud_rate_set(uint32_t baud) +{ + if (baud == 0U) { + return false; + } + + Rs485_Baud_Rate = baud; + return (uart_set_baudrate(RS485_UART_NUM, (int)Rs485_Baud_Rate) == ESP_OK); +} + +/** + * @brief Get the elapsed silent time on the RS485 bus + * @return milliseconds since the last bus activity + */ +uint32_t rs485_silence_milliseconds(void) +{ + return mstimer_elapsed(&Silence_Timer); +} + +/** + * @brief Reset the RS485 silence timer + */ +void rs485_silence_reset(void) +{ + mstimer_set(&Silence_Timer, 0); +} + +/** + * @brief Get the number of transmitted RS485 bytes + * @return transmitted byte count + */ +uint32_t rs485_bytes_transmitted(void) +{ + return Rs485_Bytes_Tx; +} + +/** + * @brief Get the number of received RS485 bytes + * @return received byte count + */ +uint32_t rs485_bytes_received(void) +{ + return Rs485_Bytes_Rx; +} diff --git a/ports/esp32/src/rs485.h b/ports/esp32/src/rs485.h new file mode 100644 index 00000000..51d2276f --- /dev/null +++ b/ports/esp32/src/rs485.h @@ -0,0 +1,100 @@ +/** + * @file + * @brief RS485 UART driver declarations used by BACnet MS/TP on ESP32 + * @author Kato Gangstad + */ + +#ifndef M5STAMPLC_RS485_H +#define M5STAMPLC_RS485_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define RS485_UART_NUM UART_NUM_1 +#define RS485_BAUD_RATE 38400 +#define RS485_TX_PIN 0 +#define RS485_RX_PIN 39 +#define RS485_DIR_PIN 46 + +/** + * @brief Initialize the RS485 UART peripheral + */ +void rs485_init(void); + +/** + * @brief Update the cached RS485 transmit-enable state + * @param enable true when transmit is active + */ +void rs485_rts_enable(bool enable); + +/** + * @brief Check whether the RS485 transmit-enable state is active + * @return true if transmit is active + */ +bool rs485_rts_enabled(void); + +/** + * @brief Read one byte from the RS485 receive FIFO when available + * @param data_register destination for the received byte, or NULL to probe only + * @return true if a byte is available + */ +bool rs485_byte_available(uint8_t *data_register); + +/** + * @brief Report whether an RS485 receive error is pending + * @return true if a receive error is detected + */ +bool rs485_receive_error(void); + +/** + * @brief Send a block of RS485 bytes + * @param buffer transmit buffer + * @param nbytes number of bytes to send + */ +void rs485_bytes_send(const uint8_t *buffer, uint16_t nbytes); + +/** + * @brief Get the configured RS485 baud rate + * @return baud rate in bits per second + */ +uint32_t rs485_baud_rate(void); + +/** + * @brief Set the RS485 baud rate + * @param baud baud rate in bits per second + * @return true if the baud rate was applied + */ +bool rs485_baud_rate_set(uint32_t baud); + +/** + * @brief Get the elapsed silent time on the RS485 bus + * @return milliseconds since the last bus activity + */ +uint32_t rs485_silence_milliseconds(void); + +/** + * @brief Reset the RS485 silence timer + */ +void rs485_silence_reset(void); + +/** + * @brief Get the number of transmitted RS485 bytes + * @return transmitted byte count + */ +uint32_t rs485_bytes_transmitted(void); + +/** + * @brief Get the number of received RS485 bytes + * @return received byte count + */ +uint32_t rs485_bytes_received(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/esp32/src/sdkconfig.h b/ports/esp32/src/sdkconfig.h deleted file mode 100644 index 64ef778b..00000000 --- a/ports/esp32/src/sdkconfig.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * - * Automatically generated file; DO NOT EDIT. - * Espressif IoT Development Framework Configuration - * - */ -#define CONFIG_GATTC_ENABLE 1 -#define CONFIG_ESP32_PHY_MAX_TX_POWER 20 -#define CONFIG_PHY_ENABLED 1 -#define CONFIG_TRACEMEM_RESERVE_DRAM 0x0 -#define CONFIG_FREERTOS_MAX_TASK_NAME_LEN 16 -#define CONFIG_BLE_SMP_ENABLE 1 -#define CONFIG_IPC_TASK_STACK_SIZE 1024 -#define CONFIG_ESPTOOLPY_FLASHFREQ "40m" -#define CONFIG_NEWLIB_STDOUT_ADDCR 1 -#define CONFIG_TASK_WDT_CHECK_IDLE_TASK 1 -#define CONFIG_ESPTOOLPY_FLASHSIZE "2MB" -#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER 1 -#define CONFIG_ETHERNET 1 -#define CONFIG_INT_WDT 1 -#define CONFIG_ESPTOOLPY_FLASHFREQ_40M 1 -#define CONFIG_LOG_BOOTLOADER_LEVEL_INFO 1 -#define CONFIG_ESPTOOLPY_FLASHSIZE_2MB 1 -#define CONFIG_AWS_IOT_MQTT_PORT 8883 -#define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 1 -#define CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM 10 -#define CONFIG_LOG_DEFAULT_LEVEL_INFO 1 -#define CONFIG_BT_RESERVE_DRAM 0x10000 -#define CONFIG_ESP32_PANIC_PRINT_REBOOT 1 -#define CONFIG_CONSOLE_UART_BAUDRATE 115200 -#define CONFIG_LWIP_MAX_SOCKETS 10 -#define CONFIG_EMAC_TASK_PRIORITY 20 -#define CONFIG_TIMER_TASK_STACK_DEPTH 2048 -#define CONFIG_FATFS_CODEPAGE 1 -#define CONFIG_ESP32_DEFAULT_CPU_FREQ_160 1 -#define CONFIG_ULP_COPROC_RESERVE_MEM 0 -#define CONFIG_ESPTOOLPY_BAUD 115200 -#define CONFIG_INT_WDT_CHECK_CPU1 1 -#define CONFIG_FLASHMODE_DIO 1 -#define CONFIG_ESPTOOLPY_AFTER_RESET 1 -#define CONFIG_TOOLPREFIX "xtensa-esp32-elf-" -#define CONFIG_FREERTOS_IDLE_TASK_STACKSIZE 1024 -#define CONFIG_ESP32_WIFI_AMPDU_ENABLED 1 -#define CONFIG_CONSOLE_UART_NUM 0 -#define CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC 1 -#define CONFIG_ESPTOOLPY_BAUD_115200B 1 -#define CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX 0 -#define CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS 1 -#define CONFIG_CONSOLE_UART_DEFAULT 1 -#define CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN 16384 -#define CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS 4 -#define CONFIG_ESPTOOLPY_FLASHSIZE_DETECT 1 -#define CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE 1 -#define CONFIG_BTDM_CONTROLLER_RUN_CPU 0 -#define CONFIG_TCPIP_TASK_STACK_SIZE 2560 -#define CONFIG_TASK_WDT 1 -#define CONFIG_MAIN_TASK_STACK_SIZE 4096 -#define CONFIG_TASK_WDT_TIMEOUT_S 5 -#define CONFIG_INT_WDT_TIMEOUT_MS 300 -#define CONFIG_ESPTOOLPY_FLASHMODE "dio" -#define CONFIG_BTC_TASK_STACK_SIZE 3072 -#define CONFIG_BLUEDROID_ENABLED 1 -#define CONFIG_ESPTOOLPY_BEFORE "default_reset" -#define CONFIG_LOG_DEFAULT_LEVEL 3 -#define CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION 1 -#define CONFIG_TIMER_QUEUE_LENGTH 10 -#define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 32 -#define CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER 20 -#define CONFIG_ESP32_RTC_CLK_CAL_CYCLES 1024 -#define CONFIG_ESP32_WIFI_NVS_ENABLED 1 -#define CONFIG_AWS_IOT_SDK 1 -#define CONFIG_DMA_RX_BUF_NUM 10 -#define CONFIG_TCP_SYNMAXRTX 6 -#define CONFIG_PYTHON "python" -#define CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 1 -#define CONFIG_ESPTOOLPY_COMPRESSED 1 -#define CONFIG_PARTITION_TABLE_FILENAME "partitions_singleapp.csv" -#define CONFIG_LWIP_DHCP_MAX_NTP_SERVERS 1 -#define CONFIG_PARTITION_TABLE_SINGLE_APP 1 -#define CONFIG_WIFI_ENABLED 1 -#define CONFIG_LWIP_DHCP_DOES_ARP_CHECK 1 -#define CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE 4096 -#define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000 -#define CONFIG_ESP32_APPTRACE_DEST_NONE 1 -#define CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET 0x10000 -#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM 32 -#define CONFIG_FATFS_CODEPAGE_ASCII 1 -#define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 1 -#define CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ 160 -#define CONFIG_FREERTOS_HZ 100 -#define CONFIG_LOG_COLORS 1 -#define CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE 1 -#define CONFIG_FREERTOS_ASSERT_FAIL_ABORT 1 -#define CONFIG_ESP32_XTAL_FREQ 0 -#define CONFIG_MONITOR_BAUD_115200B 1 -#define CONFIG_LOG_BOOTLOADER_LEVEL 3 -#define CONFIG_SMP_ENABLE 1 -#define CONFIG_ESPTOOLPY_BEFORE_RESET 1 -#define CONFIG_ESPTOOLPY_BAUD_OTHER_VAL 115200 -#define CONFIG_ESP32_XTAL_FREQ_AUTO 1 -#define CONFIG_TCP_MAXRTX 12 -#define CONFIG_ESPTOOLPY_AFTER "hard_reset" -#define CONFIG_DMA_TX_BUF_NUM 10 -#define CONFIG_ESP32_DEBUG_OCDAWARE 1 -#define CONFIG_TIMER_TASK_PRIORITY 1 -#define CONFIG_BT_ENABLED 1 -#define CONFIG_MONITOR_BAUD 115200 -#define CONFIG_FREERTOS_CORETIMER_0 1 -#define CONFIG_PARTITION_TABLE_CUSTOM_FILENAME "partitions.csv" -#define CONFIG_MBEDTLS_HAVE_TIME 1 -#define CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY 1 -#define CONFIG_GATTS_ENABLE 1 -#define CONFIG_FREERTOS_ISR_STACKSIZE 1536 -#define CONFIG_OPENSSL_ASSERT_DO_NOTHING 1 -#define CONFIG_AWS_IOT_MQTT_HOST "" -#define CONFIG_SYSTEM_EVENT_QUEUE_SIZE 32 -#define CONFIG_BT_ACL_CONNECTIONS 4 -#define CONFIG_FATFS_MAX_LFN 255 -#define CONFIG_ESP32_WIFI_TX_BUFFER_TYPE 1 -#define CONFIG_APP_OFFSET 0x10000 -#define CONFIG_MEMMAP_SMP 1 -#define CONFIG_SPI_FLASH_ROM_DRIVER_PATCH 1 -#define CONFIG_MONITOR_BAUD_OTHER_VAL 115200 -#define CONFIG_ESPTOOLPY_PORT "/dev/ttyUSB0" -#define CONFIG_OPTIMIZATION_LEVEL_RELEASE 1