From ad5a43042c2384ae34aec6f3b468e2a809878646 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Mon, 25 Aug 2025 11:02:43 -0500 Subject: [PATCH] Added MS/TP datalink option to BACnet basic server example. (#1077) --- CMakeLists.txt | 1 + Makefile | 4 ++ apps/server-basic/Makefile | 1 + ports/bsd/dlmstp.c | 38 ++++++++++-- ports/linux/dlmstp.c | 38 ++++++++++-- src/bacnet/basic/server/bacnet_port.c | 6 ++ src/bacnet/basic/server/bacnet_port_mstp.c | 70 ++++++++++++++++++++++ src/bacnet/basic/server/bacnet_port_mstp.h | 28 +++++++++ src/bacnet/datalink/dlmstp.c | 27 ++++++++- src/bacnet/datalink/dlmstp.h | 4 ++ 10 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 src/bacnet/basic/server/bacnet_port_mstp.c create mode 100644 src/bacnet/basic/server/bacnet_port_mstp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0001d589..832058b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1012,6 +1012,7 @@ if(BACNET_STACK_BUILD_APPS) src/bacnet/basic/server/bacnet_port.c src/bacnet/basic/server/bacnet_port_ipv4.c src/bacnet/basic/server/bacnet_port_ipv6.c + src/bacnet/basic/server/bacnet_port_mstp.c ) target_link_libraries(bacbasic PRIVATE ${PROJECT_NAME}) target_compile_options(bacbasic PRIVATE diff --git a/Makefile b/Makefile index b9a0d528..3195fc51 100644 --- a/Makefile +++ b/Makefile @@ -210,6 +210,10 @@ server: server-basic: $(MAKE) LEGACY=true NOTIFY=false -s -C apps $@ +.PHONY: server-basic-mstp +server-basic-mstp: + $(MAKE) LEGACY=true NOTIFY=false BACDL=mstp -s -C apps server-basic + .PHONY: server-client server-client: $(MAKE) LEGACY=true -s -C apps $@ diff --git a/apps/server-basic/Makefile b/apps/server-basic/Makefile index 00fcf710..1324e358 100644 --- a/apps/server-basic/Makefile +++ b/apps/server-basic/Makefile @@ -11,6 +11,7 @@ SRC = main.c \ $(BACNET_SERVER_DIR)/bacnet_port.c \ $(BACNET_SERVER_DIR)/bacnet_port_ipv4.c \ $(BACNET_SERVER_DIR)/bacnet_port_ipv6.c \ + $(BACNET_SERVER_DIR)/bacnet_port_mstp.c \ $(BACNET_OBJECT_DIR)/ai.c \ $(BACNET_OBJECT_DIR)/ao.c \ $(BACNET_OBJECT_DIR)/av.c \ diff --git a/ports/bsd/dlmstp.c b/ports/bsd/dlmstp.c index 1480da93..e1c7677e 100644 --- a/ports/bsd/dlmstp.c +++ b/ports/bsd/dlmstp.c @@ -70,6 +70,7 @@ static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Rx_Callback; static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Not_For_Us_Rx_Callback; static dlmstp_hook_frame_rx_complete_cb Invalid_Frame_Rx_Callback; static DLMSTP_STATISTICS DLMSTP_Statistics; +static bool DLMSTP_Initialized; /** * @brief Cleanup the MS/TP datalink @@ -87,6 +88,7 @@ void dlmstp_cleanup(void) pthread_mutex_destroy(&Receive_Packet_Mutex); pthread_mutex_destroy(&Master_Done_Mutex); pthread_mutex_destroy(&Ring_Buffer_Mutex); + DLMSTP_Initialized = false; } /** @@ -1003,6 +1005,27 @@ void dlmstp_silence_reset(void *arg) mstimer_set(&Silence_Timer, 0); } +/** + * @brief Configures the interface name + * @param ifname = the interface name + */ +void dlmstp_set_interface(const char *ifname) +{ + /* note: expects a constant char, or char from the heap */ + if (ifname) { + RS485_Set_Interface((char *)ifname); + } +} + +/** + * @brief Returns the interface name + * @return the interface name + */ +const char *dlmstp_get_interface(void) +{ + return RS485_Interface(); +} + /** * @brief Initialize this MS/TP datalink * @param ifname user data structure @@ -1013,6 +1036,17 @@ bool dlmstp_init(char *ifname) pthread_condattr_t attr; int rv = 0; + if (DLMSTP_Initialized) { + dlmstp_cleanup(); + RS485_Cleanup(); + } + DLMSTP_Initialized = true; + if (ifname) { + RS485_Set_Interface(ifname); + debug_fprintf(stderr, "MS/TP Interface: %s\n", ifname); + } else { + ifname = (char *)RS485_Interface(); + } pthread_condattr_init(&attr); // TODO use mach_absolute_time() for MONOTONIC clock if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) { @@ -1054,10 +1088,6 @@ bool dlmstp_init(char *ifname) clock_gettime(CLOCK_MONOTONIC, &Clock_Get_Time_Start); /* initialize hardware */ mstimer_set(&Silence_Timer, 0); - if (ifname) { - RS485_Set_Interface(ifname); - debug_fprintf(stderr, "MS/TP Interface: %s\n", ifname); - } RS485_Initialize(); MSTP_Port.InputBuffer = &RxBuffer[0]; MSTP_Port.InputBufferSize = sizeof(RxBuffer); diff --git a/ports/linux/dlmstp.c b/ports/linux/dlmstp.c index 38c75538..1b6eeab4 100644 --- a/ports/linux/dlmstp.c +++ b/ports/linux/dlmstp.c @@ -74,6 +74,7 @@ static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Rx_Callback; static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Not_For_Us_Rx_Callback; static dlmstp_hook_frame_rx_complete_cb Invalid_Frame_Rx_Callback; static DLMSTP_STATISTICS DLMSTP_Statistics; +static bool DLMSTP_Initialized; /** * @brief Cleanup the MS/TP datalink @@ -91,6 +92,7 @@ void dlmstp_cleanup(void) pthread_mutex_destroy(&Receive_Packet_Mutex); pthread_mutex_destroy(&Master_Done_Mutex); pthread_mutex_destroy(&Ring_Buffer_Mutex); + DLMSTP_Initialized = false; } /** @@ -1017,6 +1019,27 @@ void dlmstp_silence_reset(void *arg) mstimer_set(&Silence_Timer, 0); } +/** + * @brief Configures the interface name + * @param ifname = the interface name + */ +void dlmstp_set_interface(const char *ifname) +{ + /* note: expects a constant char, or char from the heap */ + if (ifname) { + RS485_Set_Interface((char *)ifname); + } +} + +/** + * @brief Returns the interface name + * @return the interface name + */ +const char *dlmstp_get_interface(void) +{ + return RS485_Interface(); +} + /** * @brief Initialize this MS/TP datalink * @param ifname user data structure @@ -1029,6 +1052,17 @@ bool dlmstp_init(char *ifname) pthread_condattr_t attr; int rv = 0; + if (DLMSTP_Initialized) { + dlmstp_cleanup(); + RS485_Cleanup(); + } + DLMSTP_Initialized = true; + if (ifname) { + RS485_Set_Interface(ifname); + debug_fprintf(stderr, "MS/TP Interface: %s\n", ifname); + } else { + ifname = (char *)RS485_Interface(); + } pthread_condattr_init(&attr); if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) { fprintf( @@ -1070,10 +1104,6 @@ bool dlmstp_init(char *ifname) clock_gettime(CLOCK_MONOTONIC, &Clock_Get_Time_Start); /* initialize hardware */ mstimer_set(&Silence_Timer, 0); - if (ifname) { - RS485_Set_Interface(ifname); - debug_fprintf(stderr, "MS/TP Interface: %s\n", ifname); - } RS485_Initialize(); MSTP_Port.InputBuffer = &RxBuffer[0]; MSTP_Port.InputBufferSize = sizeof(RxBuffer); diff --git a/src/bacnet/basic/server/bacnet_port.c b/src/bacnet/basic/server/bacnet_port.c index ba2cc136..f8b18ce9 100644 --- a/src/bacnet/basic/server/bacnet_port.c +++ b/src/bacnet/basic/server/bacnet_port.c @@ -20,6 +20,8 @@ #include "bacnet/basic/server/bacnet_port_ipv4.h" #elif defined(BACDL_BIP6) #include "bacnet/basic/server/bacnet_port_ipv6.h" +#elif defined(BACDL_MSTP) +#include "bacnet/basic/server/bacnet_port_mstp.h" #endif /* me! */ #include "bacnet/basic/server/bacnet_port.h" @@ -45,6 +47,8 @@ void bacnet_port_task(void) bacnet_port_ipv4_task(elapsed_seconds); #elif defined(BACDL_BIP6) bacnet_port_ipv6_task(elapsed_seconds); +#elif defined(BACDL_MSTP) + bacnet_port_mstp_task(elapsed_seconds); #else /* nothing to do */ (void)elapsed_seconds; @@ -64,6 +68,8 @@ bool bacnet_port_init(void) status = bacnet_port_ipv4_init(); #elif defined(BACDL_BIP6) status = bacnet_port_ipv6_init(); +#elif defined(BACDL_MSTP) + status = bacnet_port_mstp_init(); #endif return status; } diff --git a/src/bacnet/basic/server/bacnet_port_mstp.c b/src/bacnet/basic/server/bacnet_port_mstp.c new file mode 100644 index 00000000..d97b87d7 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_mstp.c @@ -0,0 +1,70 @@ +/** + * @file + * @brief The BACnet datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date January 2025 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/object/netport.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlmstp.h" +/* BACnet Basic network port */ +#include "bacnet/basic/server/bacnet_port_mstp.h" + +#if defined(BACDL_MSTP) +static struct dlmstp_statistics Statistics = { 0 }; +static uint16_t Timer_Seconds; + +/** + * @brief Application Task + */ +void bacnet_port_mstp_task(uint16_t elapsed_seconds) +{ + Timer_Seconds += elapsed_seconds; + if (Timer_Seconds >= 60) { + Timer_Seconds = 0; + dlmstp_fill_statistics(&Statistics); + } +} + +/** + * Initialize the network port object. + * @return true if successful + */ +bool bacnet_port_mstp_init(void) +{ + uint32_t instance = 1; + BACNET_ADDRESS addr = { 0 }; + + if (!dlmstp_init(NULL)) { + return false; + } + Network_Port_Object_Instance_Number_Set(0, instance); + Network_Port_Name_Set(instance, "BACnet MS/TP Port"); + Network_Port_Type_Set(instance, PORT_TYPE_MSTP); + dlmstp_get_my_address(&addr); + Network_Port_MAC_Address_Set(instance, &addr.mac[0], addr.mac_len); + Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); + Network_Port_Out_Of_Service_Set(instance, false); + Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN); + Network_Port_APDU_Length_Set(instance, MAX_APDU); + Network_Port_Network_Number_Set(instance, 0); + /* MS/TP specific */ + Network_Port_Link_Speed_Set(instance, dlmstp_baud_rate()); + Network_Port_MSTP_Max_Info_Frames_Set(instance, dlmstp_max_info_frames()); + Network_Port_MSTP_Max_Master_Set(instance, dlmstp_max_master()); + /* last thing - clear pending changes - we don't want to set these + since they are already set */ + Network_Port_Changes_Pending_Set(instance, false); + + return true; +} +#endif diff --git a/src/bacnet/basic/server/bacnet_port_mstp.h b/src/bacnet/basic/server/bacnet_port_mstp.h new file mode 100644 index 00000000..139eefad --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_mstp.h @@ -0,0 +1,28 @@ +/** + * @file + * @brief The BACnet MS/TP datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date January 2025 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BACNET_PORT_MSTP_H +#define BACNET_PORT_MSTP_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/datalink/dlmstp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_port_mstp_task(uint16_t elapsed_seconds); +bool bacnet_port_mstp_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/datalink/dlmstp.c b/src/bacnet/datalink/dlmstp.c index 39f1e0aa..929f8f3d 100644 --- a/src/bacnet/datalink/dlmstp.c +++ b/src/bacnet/datalink/dlmstp.c @@ -1094,6 +1094,24 @@ void dlmstp_silence_reset(void *arg) } } +/** + * @brief set the MS/TP datalink interface + * @param ifname - interface name to set + */ +void dlmstp_set_interface(const char *ifname) +{ + MSTP_Port = (struct mstp_port_struct_t *)ifname; +} + +/** + * @brief get the MS/TP datalink intferface name + * @return interface name + */ +const char *dlmstp_get_interface(void) +{ + return (const char *)MSTP_Port; +} + /** * @brief Initialize this MS/TP datalink * @param ifname user data structure @@ -1101,8 +1119,12 @@ void dlmstp_silence_reset(void *arg) */ bool dlmstp_init(char *ifname) { + bool status = false; struct dlmstp_user_data_t *user; - MSTP_Port = (struct mstp_port_struct_t *)ifname; + + if (ifname) { + MSTP_Port = (struct mstp_port_struct_t *)ifname; + } if (MSTP_Port) { MSTP_Port->SilenceTimer = dlmstp_silence_milliseconds; MSTP_Port->SilenceTimerReset = dlmstp_silence_reset; @@ -1119,7 +1141,8 @@ bool dlmstp_init(char *ifname) MSTP_Init(MSTP_Port); user->Initialized = true; } + status = true; } - return true; + return status; } diff --git a/src/bacnet/datalink/dlmstp.h b/src/bacnet/datalink/dlmstp.h index 235009da..8a2bab4c 100644 --- a/src/bacnet/datalink/dlmstp.h +++ b/src/bacnet/datalink/dlmstp.h @@ -120,6 +120,10 @@ extern "C" { BACNET_STACK_EXPORT bool dlmstp_init(char *ifname); BACNET_STACK_EXPORT +void dlmstp_set_interface(const char *ifname); +BACNET_STACK_EXPORT +const char *dlmstp_get_interface(void); +BACNET_STACK_EXPORT void dlmstp_reset(void); BACNET_STACK_EXPORT void dlmstp_cleanup(void);