Chore/refactor dlmstp core mstp (#559)

* Create common DLMSTP using core MSTP FSM in STM32F4xx example

* add openocd debug launcher under vscode in STM32F4xx example

* Add generic property list member checking for write property members of network port object in STM32F4xx example
This commit is contained in:
Steve Karg
2024-01-27 15:16:42 -06:00
committed by GitHub
parent 587e3c5a11
commit ef762118a6
14 changed files with 44276 additions and 1756 deletions
+45
View File
@@ -0,0 +1,45 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug with OpenOCD",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/build/bacnet-mstp.elf",
"servertype": "openocd",
"device": "STM32F429ZI",
"serialNumber": "",
"configFiles": [
"interface/stlink.cfg",
"target/stm32f4x.cfg"
],
"searchDir": [],
"svdFile":"${workspaceRoot}/stm32f429.svd",
"runToEntryPoint": "main",
"swoConfig": {
"enabled": true,
"cpuFrequency": 8000000,
"swoFrequency": 2000000,
"source": "probe",
"decoders": [
{ "type": "console", "label": "ITM", "port": 0 }
]
},
"preLaunchCommands": [
// guarantee the halt at soon as possible after reset
"monitor reset",
"monitor sleep 2000",
"monitor reset halt",
// synchronize GDB to the state of the target after reset
"monitor gdb_sync",
"stepi"
],
"postLaunchCommands": [
"monitor reset init",
"monitor sleep 200"
],
"showDevDebugOutput": "raw"
}
]
}
+5 -2
View File
@@ -139,7 +139,6 @@ set(BACNET_PROJECT_SOURCE
${CMAKE_SOURCE_DIR}/system_stm32f4xx.h
${CMAKE_SOURCE_DIR}/bacnet.c
${CMAKE_SOURCE_DIR}/dlmstp.c
${CMAKE_SOURCE_DIR}/led.c
${CMAKE_SOURCE_DIR}/mstimer-init.c
${CMAKE_SOURCE_DIR}/rs485.c
@@ -177,8 +176,12 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_CORE}/bacstr.c
${LIBRARY_BACNET_CORE}/datalink/cobs.c
${LIBRARY_BACNET_CORE}/datalink/crc.c
${LIBRARY_BACNET_CORE}/datalink/dlmstp.c
${LIBRARY_BACNET_CORE}/datalink/mstp.c
${LIBRARY_BACNET_CORE}/datalink/mstptext.c
${LIBRARY_BACNET_CORE}/datetime.c
${LIBRARY_BACNET_CORE}/dcc.c
${LIBRARY_BACNET_CORE}/indtext.c
${LIBRARY_BACNET_CORE}/iam.c
${LIBRARY_BACNET_CORE}/ihave.c
${LIBRARY_BACNET_CORE}/hostnport.c
@@ -204,7 +207,7 @@ set(BACNET_PROJECT_SOURCE
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/stm32f4xx.ld)
set(EXECUTABLE ${PROJECT_NAME}.out)
set(EXECUTABLE ${PROJECT_NAME}.elf)
add_executable(${EXECUTABLE} ${BACNET_PROJECT_SOURCE})
+14 -6
View File
@@ -22,7 +22,6 @@ PLATFORM_SRC = \
$(PLATFORM_DIR)/main.c \
$(PLATFORM_DIR)/bacnet.c \
$(PLATFORM_DIR)/device.c \
$(PLATFORM_DIR)/dlmstp.c \
$(PLATFORM_DIR)/led.c \
$(PLATFORM_DIR)/netport.c \
$(PLATFORM_DIR)/rs485.c \
@@ -31,9 +30,9 @@ PLATFORM_SRC = \
$(PLATFORM_DIR)/system_stm32f4xx.c
BASIC_SRC = \
$(BACNET_BASIC)/service/h_dcc.c \
$(BACNET_BASIC)/service/h_apdu.c \
$(BACNET_BASIC)/npdu/h_npdu.c \
$(BACNET_BASIC)/service/h_apdu.c \
$(BACNET_BASIC)/service/h_dcc.c \
$(BACNET_BASIC)/service/h_rd.c \
$(BACNET_BASIC)/service/h_rp.c \
$(BACNET_BASIC)/service/h_rpm.c \
@@ -43,11 +42,11 @@ BASIC_SRC = \
$(BACNET_BASIC)/service/h_noserv.c \
$(BACNET_BASIC)/service/s_iam.c \
$(BACNET_BASIC)/service/s_ihave.c \
$(BACNET_BASIC)/tsm/tsm.c \
$(BACNET_BASIC)/sys/debug.c \
$(BACNET_BASIC)/sys/ringbuf.c \
$(BACNET_BASIC)/sys/fifo.c \
$(BACNET_BASIC)/sys/mstimer.c
$(BACNET_BASIC)/sys/mstimer.c \
$(BACNET_BASIC)/tsm/tsm.c
BACNET_SRC = \
$(BACNET_CORE)/abort.c \
@@ -62,8 +61,12 @@ BACNET_SRC = \
$(BACNET_CORE)/bacstr.c \
$(BACNET_CORE)/datalink/cobs.c \
$(BACNET_CORE)/datalink/crc.c \
$(BACNET_CORE)/datalink/dlmstp.c \
$(BACNET_CORE)/datalink/mstp.c \
$(BACNET_CORE)/datalink/mstptext.c \
$(BACNET_CORE)/datetime.c \
$(BACNET_CORE)/dcc.c \
$(BACNET_CORE)/indtext.c \
$(BACNET_CORE)/iam.c \
$(BACNET_CORE)/ihave.c \
$(BACNET_CORE)/hostnport.c \
@@ -210,11 +213,16 @@ GDB_PORT = 3333
debug:
st-util --listen $(GDB_PORT)
# Note: STLink is built into Nucleo board
OPENOCD_FLAGS = -f interface/stlink.cfg -f target/stm32f4x.cfg
# GDB using openocd (GDB server for ST Link)
# sudo apt install openocd
.PHONY: openocd
openocd:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
openocd $(OPENOCD_FLAGS)
flash: $(TARGET).elf
openocd $(OPENOCD_FLAGS) -c "program $< verify reset" -c "shutdown"
# graphical GDB debugging tool
# note: relies on .gdbinit containing:
+47
View File
@@ -56,3 +56,50 @@ The NUCLEO-F429ZI platform includes the following peripherals:
| CN10-12 | PF15 | D2 | CE (DFR0259)
| CN10-14 | PG14 | D1 | TXD
| CN10-16 | PG9 | D0 | RXD
### Building this Project
#### IAR EWARM Project
There is an IAR EWARM project bacnet.ewp that can be used to build the project.
#### GNU Makefile
There is a GNU Makefile that uses arm-none-eabi-gcc to build the project.
It also includes recipes for openocd or stlink, and gdb or ddd for debugging.
This is used in the continuous integration pipeline to validate the build
is not broken. The Makefile is called from an Ubuntu image container
after installing the necesary tools:
sudo apt-get update -qq
sudo apt-get install -qq build-essential
sudo apt-get install -qq gcc-arm-none-eabi
sudo apt-get install -qq libnewlib-arm-none-eabi
For debugging, install these tools:
sudo apt-get update -qq
sudo apt-get install -qq openocd gdb-multiarch
#### CMake & Visual Studio Code
There is a CMakeLists.txt file that enables building the project with the
tools that CMake can find. It is useful under Visual Studio Code editor
with the CMake Tools extension for quickly configuring the build environment,
choosing a cross-compiler, and building.
For Visual Studio Code debugging, add the Cortex-Debug extension, and configure
its settings for the specific OS and path of the tools. For Windows using
MinGW64:
"cortex-debug.armToolchainPath.windows": "C:/msys64/mingw64/bin",
"cortex-debug.gdbPath.windows": "C:/msys64/mingw64/bin/gdb-multiarch.exe",
"cortex-debug.objdumpPath.windows": "C:/msys64/mingw64/bin/objdump.exe",
"cortex-debug.openocdPath.windows": "C:/msys64/mingw64/bin/openocd.exe",
To add the build and debug tools to MinGW64 environment:
pacman --noconfirm -S mingw-w64-x86_64-arm-none-eabi-gcc
pacman --noconfirm -S mingw-w64-x86_64-openocd
pacman --noconfirm -S mingw-w64-x86_64-gdb-multiarch
+22 -9
View File
@@ -44,13 +44,14 @@
/* timer for device communications control */
static struct mstimer DCC_Timer;
#define DCC_CYCLE_SECONDS 1
/* Device ID to track changes */
static uint32_t Device_ID = 0xFFFFFFFF;
/**
* @brief Initialize the BACnet device object, the service handlers, and timers
*/
void bacnet_init(void)
{
dlmstp_set_mac_address(2);
dlmstp_set_max_master(127);
/* initialize datalink layer */
dlmstp_init(NULL);
/* initialize objects */
Device_Init(NULL);
@@ -74,23 +75,35 @@ void bacnet_init(void)
handler_device_communication_control);
/* start the cyclic 1 second timer for DCC */
mstimer_set(&DCC_Timer, DCC_CYCLE_SECONDS * 1000);
/* Hello World! */
Send_I_Am(&Handler_Transmit_Buffer[0]);
}
/* local buffer for incoming PDUs to process */
static uint8_t PDUBuffer[MAX_MPDU];
/**
* @brief non-blocking BACnet task
*/
void bacnet_task(void)
{
uint16_t pdu_len;
BACNET_ADDRESS src; /* source address */
bool hello_world = false;
uint16_t pdu_len = 0;
BACNET_ADDRESS src = { 0 };
/* hello, World! */
if (Device_ID != Device_Object_Instance_Number()) {
Device_ID = Device_Object_Instance_Number();
hello_world = true;
}
if (hello_world) {
Send_I_Am(&Handler_Transmit_Buffer[0]);
}
/* handle the communication timer */
if (mstimer_expired(&DCC_Timer)) {
mstimer_reset(&DCC_Timer);
dcc_timer_seconds(DCC_CYCLE_SECONDS);
}
/* handle the messaging */
pdu_len = dlmstp_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0);
pdu_len = datalink_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0);
if (pdu_len) {
npdu_handler(&src, &PDUBuffer[0], pdu_len);
}
+18 -9
View File
@@ -1087,6 +1087,9 @@
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\dcc.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\indtext.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\iam.c</name>
</file>
@@ -1144,6 +1147,15 @@
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\datalink\crc.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\datalink\dlmstp.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\datalink\mstp.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\src\bacnet\datalink\mstptext.c</name>
</file>
</group>
<group>
<name>BACnet NPDU Handler</name>
@@ -1222,6 +1234,12 @@
<file>
<name>$PROJ_DIR$\rs485.c</name>
</file>
<file>
<name>$PROJ_DIR$\stm32f4xx_it.c</name>
</file>
<file>
<name>$PROJ_DIR$\system_stm32f4xx.c</name>
</file>
</group>
<group>
<name>NUCLEO-BACnet</name>
@@ -1231,9 +1249,6 @@
<file>
<name>$PROJ_DIR$\device.c</name>
</file>
<file>
<name>$PROJ_DIR$\dlmstp.c</name>
</file>
<file>
<name>$PROJ_DIR$\netport.c</name>
</file>
@@ -1280,10 +1295,4 @@
<file>
<name>$PROJ_DIR$\main.c</name>
</file>
<file>
<name>$PROJ_DIR$\stm32f4xx_it.c</name>
</file>
<file>
<name>$PROJ_DIR$\system_stm32f4xx.c</name>
</file>
</project>
File diff suppressed because it is too large Load Diff
+46 -2
View File
@@ -25,21 +25,51 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "stm32f4xx.h"
#include "stm32f4xx_pwr.h"
#include "stm32f4xx_rcc.h"
#include "system_stm32f4xx.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/sys/ringbuf.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/datalink/dlmstp.h"
#include "bacnet/datalink/mstp.h"
#include "rs485.h"
#include "led.h"
#include "bacnet.h"
/* MS/TP port */
static struct mstp_port_struct_t MSTP_Port;
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
};
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];
/**
* @brief Called from _write() function from printf and friends
* @param[in] ch Character to send
*/
int __io_putchar(int ch)
{
(void)ch;
return 0;
}
/**
* @brief Main function
* @return 0 - never returns
*/
int main(void)
{
struct mstimer Blink_Timer;
@@ -53,12 +83,26 @@ int main(void)
/* enable some clocks - USART and GPIO clocks are enabled in our drivers */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* enable our hardware */
/* initialize hardware layer */
mstimer_init();
led_init();
rs485_init();
mstimer_set(&Blink_Timer, 500);
/* initialize MSTP datalink layer */
MSTP_Port.Nmax_info_frames = DLMSTP_MAX_INFO_FRAMES;
MSTP_Port.Nmax_master = DLMSTP_MAX_MASTER;
MSTP_Port.InputBuffer = Input_Buffer;
MSTP_Port.InputBufferSize = sizeof(Input_Buffer);
MSTP_Port.OutputBuffer = Output_Buffer;
MSTP_Port.OutputBufferSize = sizeof(Output_Buffer);
/* user data */
MSTP_User_Data.RS485_Driver = &RS485_Driver;
MSTP_Port.UserData = &MSTP_User_Data;
dlmstp_init((char *)&MSTP_Port);
dlmstp_set_mac_address(2);
dlmstp_set_baud_rate(DLMSTP_BAUD_RATE_DEFAULT);
/* initialize application layer*/
bacnet_init();
mstimer_set(&Blink_Timer, 125);
for (;;) {
if (mstimer_expired(&Blink_Timer)) {
mstimer_reset(&Blink_Timer);
+43 -27
View File
@@ -79,11 +79,16 @@ static const int Network_Port_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
PROP_APDU_LENGTH, PROP_LINK_SPEED, -1 };
static const int Network_Port_Properties_Optional[] = { PROP_MAC_ADDRESS,
PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, PROP_LINK_SPEEDS,
-1 };
PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, PROP_LINK_SPEEDS, -1 };
static const int Network_Port_Properties_Proprietary[] = { -1 };
/* standard properties that are arrays for this object,
but not necessary supported in this object */
static const int Network_Port_Properties_Array[] = { PROP_LINK_SPEEDS,
PROP_IP_DNS_SERVER, PROP_IPV6_DNS_SERVER, PROP_EVENT_MESSAGE_TEXTS,
PROP_EVENT_MESSAGE_TEXTS_CONFIG, PROP_TAGS, -1 };
/**
* Returns the list of required, optional, and proprietary properties.
* Used by ReadPropertyMultiple service.
@@ -522,6 +527,33 @@ bool Network_Port_MSTP_Max_Info_Frames_Set(
return status;
}
/**
* @brief Determine if the object property is a member of this object instance
* @param object_instance - object-instance number of the object
* @param object_property - object-property to be checked
* @return true if the property is a member of this object instance
*/
static bool Network_Port_Property_List_Member(
uint32_t object_instance, int object_property)
{
bool found = false;
const int *pRequired = NULL;
const int *pOptional = NULL;
const int *pProprietary = NULL;
Network_Port_Property_List(
object_instance, &pRequired, &pOptional, &pProprietary);
found = property_list_member(pRequired, object_property);
if (!found) {
found = property_list_member(pOptional, object_property);
}
if (!found) {
found = property_list_member(pProprietary, object_property);
}
return found;
}
/**
* ReadProperty handler for this object. For the given ReadProperty
* data, the application_data is loaded or the error flags are set.
@@ -619,9 +651,8 @@ int Network_Port_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
case PROP_LINK_SPEEDS:
count = Network_Port_Link_Speeds_Count(rpdata->object_instance);
apdu_len = bacnet_array_encode(rpdata->object_instance,
rpdata->array_index,
Network_Port_Link_Speeds_Encode,
count, apdu, apdu_max);
rpdata->array_index, Network_Port_Link_Speeds_Encode, count,
apdu, apdu_max);
if (apdu_len == BACNET_STATUS_ABORT) {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
@@ -685,12 +716,8 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
return false;
}
if ((wp_data->object_property != PROP_LINK_SPEEDS) &&
(wp_data->object_property != PROP_IP_DNS_SERVER) &&
(wp_data->object_property != PROP_IPV6_DNS_SERVER) &&
(wp_data->object_property != PROP_EVENT_MESSAGE_TEXTS) &&
(wp_data->object_property != PROP_EVENT_MESSAGE_TEXTS_CONFIG) &&
(wp_data->object_property != PROP_TAGS) &&
if (!property_list_member(
Network_Port_Properties_Array, wp_data->object_property) &&
(wp_data->array_index != BACNET_ARRAY_ALL)) {
/* only array properties can have array options */
wp_data->error_class = ERROR_CLASS_PROPERTY;
@@ -763,26 +790,15 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_OBJECT_IDENTIFIER:
case PROP_OBJECT_NAME:
case PROP_OBJECT_TYPE:
case PROP_STATUS_FLAGS:
case PROP_RELIABILITY:
case PROP_OUT_OF_SERVICE:
case PROP_NETWORK_TYPE:
case PROP_PROTOCOL_LEVEL:
case PROP_NETWORK_NUMBER:
case PROP_NETWORK_NUMBER_QUALITY:
case PROP_MAX_APDU_LENGTH_ACCEPTED:
case PROP_CHANGES_PENDING:
case PROP_APDU_LENGTH:
case PROP_LINK_SPEEDS:
default:
if (Network_Port_Property_List_Member(
wp_data->object_instance, wp_data->object_property)) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
break;
default:
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
}
break;
}
+5 -36
View File
@@ -68,38 +68,12 @@ void rs485_silence_reset(void)
}
/**
* @brief Determine the amount of silence on the wire from the timer.
* @param amount of time that might have elapsed
* @return true if the amount of time has elapsed
* @brief Return the RS-485 silence time in milliseconds
* @return silence time in milliseconds
*/
bool rs485_silence_elapsed(uint32_t interval)
uint32_t rs485_silence_milliseconds(void)
{
return (mstimer_elapsed(&Silence_Timer) > interval);
}
/**
* @brief Determine the turnaround time
* @return amount of milliseconds
*/
static uint16_t rs485_turnaround_time(void)
{
/* delay after reception before transmitting - per MS/TP spec */
/* wait a minimum 40 bit times since reception */
/* at least 2 ms for errors: rounding, clock tick */
if (Baud_Rate) {
return (2 + ((Tturnaround * 1000UL) / Baud_Rate));
} else {
return 2;
}
}
/**
* @brief Use the silence timer to determine turnaround time
* @return true if turnaround time has expired
*/
bool rs485_turnaround_elapsed(void)
{
return (mstimer_elapsed(&Silence_Timer) > rs485_turnaround_time());
return mstimer_elapsed(&Silence_Timer);
}
/**
@@ -216,10 +190,8 @@ bool rs485_byte_available(uint8_t *data_register)
* @param nbytes - number of bytes to transmit
* @return true if added to queue
*/
bool rs485_bytes_send(uint8_t *buffer, uint16_t nbytes)
void rs485_bytes_send(uint8_t *buffer, uint16_t nbytes)
{
bool status = false;
if (buffer && (nbytes > 0)) {
if (FIFO_Add(&Transmit_Queue, buffer, nbytes)) {
rs485_silence_reset();
@@ -229,11 +201,8 @@ bool rs485_bytes_send(uint8_t *buffer, uint16_t nbytes)
/* enable the USART to generate interrupts on TX empty */
USART_ITConfig(USART6, USART_IT_TXE, ENABLE);
/* TXE interrupt will load the first byte */
status = true;
}
}
return status;
}
/**
+3 -5
View File
@@ -47,16 +47,14 @@ void rs485_rts_enable(bool enable);
bool rs485_rts_enabled(void);
bool rs485_byte_available(uint8_t *data_register);
bool rs485_receive_error(void);
bool rs485_bytes_send(uint8_t *buffer, /* data to send */
uint16_t nbytes); /* number of bytes of data */
void rs485_bytes_send(uint8_t *buffer, uint16_t nbytes);
uint32_t rs485_baud_rate(void);
bool rs485_baud_rate_set(uint32_t baud);
uint32_t rs485_silence_milliseconds(void);
void rs485_silence_reset(void);
bool rs485_silence_elapsed(uint32_t milliseconds);
void rs485_silence_increment(unsigned int milliseconds);
bool rs485_turnaround_elapsed(void);
uint32_t rs485_bytes_transmitted(void);
uint32_t rs485_bytes_received(void);
File diff suppressed because it is too large Load Diff
+840
View File
@@ -0,0 +1,840 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date February 2023
* @brief Datalink MS/TP Interface
*
* SPDX-License-Identifier: MIT
*
*/
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "bacnet/basic/sys/ringbuf.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/datalink/crc.h"
#include "bacnet/datalink/mstp.h"
#include "bacnet/datalink/dlmstp.h"
#include "bacnet/datalink/mstpdef.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bits.h"
#include "bacnet/bytes.h"
#include "bacnet/bacaddr.h"
/* the current MSTP port that the datalink is using */
static struct mstp_port_struct_t *MSTP_Port;
/**
* @brief send an PDU via MSTP
* @param dest - BACnet destination address
* @param npdu_data - network layer information
* @param pdu - PDU data to send
* @param pdu_len - number of bytes of PDU data to send
* @return number of bytes sent on success, zero on failure
*/
int dlmstp_send_pdu(BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len)
{
int bytes_sent = 0;
unsigned i = 0; /* loop counter */
struct dlmstp_user_data_t *port = NULL;
struct dlmstp_packet *pkt;
if (!MSTP_Port) {
return 0;
}
if (!MSTP_Port->UserData) {
return 0;
}
port = MSTP_Port->UserData;
pkt = (struct dlmstp_packet *)(void *)Ringbuf_Data_Peek(&port->PDU_Queue);
if (pkt && (pdu_len <= DLMSTP_MPDU_MAX)) {
if (npdu_data->data_expecting_reply) {
pkt->frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
} else {
pkt->frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
}
for (i = 0; i < pdu_len; i++) {
pkt->pdu[i] = pdu[i];
}
pkt->pdu_len = pdu_len;
if (dest && dest->mac_len) {
pkt->address.mac_len = 1;
pkt->address.mac[0] = dest->mac[0];
pkt->address.len = 0;
} else {
pkt->address.mac_len = 1;
pkt->address.mac[0] = MSTP_BROADCAST_ADDRESS;
pkt->address.len = 0;
}
if (Ringbuf_Data_Put(&port->PDU_Queue, (uint8_t *)pkt)) {
bytes_sent = pdu_len;
}
}
return bytes_sent;
}
/**
* @brief The MS/TP state machine uses this function for getting data to send
* @param mstp_port - specific MSTP port that is used for this datalink
* @param timeout - number of milliseconds to wait for the data
* @return amount of PDU data
*/
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t *mstp_port, unsigned timeout)
{
uint16_t pdu_len = 0;
struct dlmstp_packet *pkt;
struct dlmstp_user_data_t *port;
if (!mstp_port) {
return 0;
}
port = (struct dlmstp_user_data_t *)mstp_port->UserData;
if (!port) {
return 0;
}
if (Ringbuf_Empty(&port->PDU_Queue)) {
return 0;
}
/* look at next PDU in queue without removing it */
pkt = (struct dlmstp_packet *)(void *)Ringbuf_Peek(&port->PDU_Queue);
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0],
mstp_port->OutputBufferSize, pkt->frame_type, pkt->address.mac[0],
mstp_port->This_Station, &pkt->pdu[0], pkt->pdu_len);
port->Statistics.transmit_pdu_counter++;
(void)Ringbuf_Pop(&port->PDU_Queue, NULL);
return pdu_len;
}
/**
* @brief Determine if the reply packet is the data expected
* @param mstp_port - specific MSTP port that is used for this datalink
* @param reply_pdu - PDU of the data
* @param reply_pdu_len - number of bytes of PDU data
* @param dest_address - the destination address for this data
* @return true if the reply packet is the data expected
*/
static bool MSTP_Compare_Data_Expecting_Reply(
volatile struct mstp_port_struct_t *mstp_port,
uint8_t *reply_pdu,
uint16_t reply_pdu_len,
BACNET_ADDRESS *dest_address)
{
uint16_t offset;
/* One way to check the message is to compare NPDU
src, dest, along with the APDU type, invoke id.
Seems a bit overkill */
struct DER_compare_t {
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS address;
uint8_t pdu_type;
uint8_t invoke_id;
uint8_t service_choice;
};
struct DER_compare_t request;
struct DER_compare_t reply;
uint8_t *request_pdu;
uint16_t request_pdu_len;
uint8_t src_address;
request_pdu = &mstp_port->InputBuffer[0];
request_pdu_len = mstp_port->DataLength;
src_address = mstp_port->SourceAddress;
/* unused parameters */
request_pdu_len = request_pdu_len;
reply_pdu_len = reply_pdu_len;
/* decode the request data */
request.address.mac[0] = src_address;
request.address.mac_len = 1;
offset = (uint16_t)npdu_decode(
&request_pdu[0], NULL, &request.address, &request.npdu_data);
if (request.npdu_data.network_layer_message) {
return false;
}
request.pdu_type = request_pdu[offset] & 0xF0;
if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
return false;
}
request.invoke_id = request_pdu[offset + 2];
/* segmented message? */
if (request_pdu[offset] & BIT(3))
request.service_choice = request_pdu[offset + 5];
else
request.service_choice = request_pdu[offset + 3];
/* decode the reply data */
bacnet_address_copy(&reply.address, dest_address);
offset = (uint16_t)npdu_decode(
&reply_pdu[0], &reply.address, NULL, &reply.npdu_data);
if (reply.npdu_data.network_layer_message) {
return false;
}
/* reply could be a lot of things:
confirmed, simple ack, abort, reject, error */
reply.pdu_type = reply_pdu[offset] & 0xF0;
switch (reply.pdu_type) {
case PDU_TYPE_SIMPLE_ACK:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_COMPLEX_ACK:
reply.invoke_id = reply_pdu[offset + 1];
/* segmented message? */
if (reply_pdu[offset] & BIT(3))
reply.service_choice = reply_pdu[offset + 4];
else
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_ERROR:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_REJECT:
case PDU_TYPE_ABORT:
reply.invoke_id = reply_pdu[offset + 1];
break;
default:
return false;
}
/* these don't have service choice included */
if ((reply.pdu_type == PDU_TYPE_REJECT) ||
(reply.pdu_type == PDU_TYPE_ABORT)) {
if (request.invoke_id != reply.invoke_id) {
return false;
}
} else {
if (request.invoke_id != reply.invoke_id) {
return false;
}
if (request.service_choice != reply.service_choice) {
return false;
}
}
if (request.npdu_data.protocol_version !=
reply.npdu_data.protocol_version) {
return false;
}
#if 0
/* the NDPU priority doesn't get passed through the stack, and
all outgoing messages have NORMAL priority */
if (request.npdu_data.priority != reply.npdu_data.priority) {
return false;
}
#endif
if (!bacnet_address_same(&request.address, &reply.address)) {
return false;
}
return true;
}
/**
* @brief The MS/TP state machine uses this function for getting data to send
* as the reply to a DATA_EXPECTING_REPLY frame, or nothing
* @param mstp_port MSTP port structure for this port
* @param timeout number of milliseconds to wait for a packet
* @return number of bytes, or 0 if no reply is available
*/
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t *mstp_port, unsigned timeout)
{
uint16_t pdu_len = 0;
bool matched = false;
struct dlmstp_packet packet = { 0 };
struct dlmstp_user_data_t *port = NULL;
struct dlmstp_packet *pkt;
if (!mstp_port) {
return 0;
}
port = mstp_port->UserData;
if (!port) {
return 0;
}
if (Ringbuf_Empty(&port->PDU_Queue)) {
return 0;
}
/* look at next PDU in queue without removing it */
pkt = (struct dlmstp_packet *)(void *)Ringbuf_Peek(&port->PDU_Queue);
/* is this the reply to the DER? */
matched = MSTP_Compare_Data_Expecting_Reply(
mstp_port, pkt->pdu, pkt->pdu_len, &pkt->address);
if (!matched) {
return 0;
}
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0],
mstp_port->OutputBufferSize, pkt->frame_type, packet.address.mac[0],
mstp_port->This_Station, &pkt->pdu[0], pkt->pdu_len);
port->Statistics.transmit_pdu_counter++;
(void)Ringbuf_Pop(&port->PDU_Queue, NULL);
return pdu_len;
}
/**
* @brief MS/TP state machine callback to use for sending a frame
* @param mstp_port - specific MSTP port that is used for this datalink
* @param buffer - buffer to send
* @param nbytes - number of bytes of data to send
*/
void MSTP_Send_Frame(volatile struct mstp_port_struct_t *mstp_port,
uint8_t *buffer,
uint16_t nbytes)
{
struct dlmstp_user_data_t *port;
struct dlmstp_rs485_driver *driver;
if (!mstp_port) {
return;
}
port = mstp_port->UserData;
if (!port) {
return;
}
driver = port->RS485_Driver;
if (!driver) {
return;
}
driver->send(buffer, nbytes);
port->Statistics.transmit_frame_counter++;
}
/**
* @brief MS/TP state machine received a frame
* @return number of bytes queued, or 0 if unable to be queued
*/
uint16_t MSTP_Put_Receive(volatile struct mstp_port_struct_t *mstp_port)
{
struct dlmstp_user_data_t *port = NULL;
if (!mstp_port) {
return 0;
}
port = mstp_port->UserData;
if (!port) {
return 0;
}
port->ReceivePacketPending = true;
return mstp_port->DataLength;
}
/**
* @brief Baud rate determines turnaround time.
* The minimum time after the end of the stop bit of the final octet of a
* received frame before a node may enable its EIA-485 driver: 40 bit times.
* At 9600 baud, 40 bit times would be about 4.166 milliseconds
* At 19200 baud, 40 bit times would be about 2.083 milliseconds
* At 38400 baud, 40 bit times would be about 1.041 milliseconds
* At 57600 baud, 40 bit times would be about 0.694 milliseconds
* At 76800 baud, 40 bit times would be about 0.520 milliseconds
* At 115200 baud, 40 bit times would be about 0.347 milliseconds
* 40 bits is 4 octets including a start and stop bit with each octet
* @param baud_rate - baud rate in bits per second
* @return: amount of whole milliseconds to wait 1..5
*/
static uint32_t dlmstp_receive_turnaround_time(uint32_t baud_rate)
{
if (baud_rate == 0) {
baud_rate = 9600;
}
return (1 + ((Tturnaround * 1000) / baud_rate));
}
/**
* @brief Run the MS/TP state machines, and get packet if available
* @param pdu - place to put PDU data for the caller
* @param max_pdu - number of bytes of PDU data that caller can receive
* @return number of bytes in received packet, or 0 if no packet was received
* @note Must be called at least once every 1 milliseconds, with no more than
* 5 milliseconds jitter.
*/
uint16_t dlmstp_receive(
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout)
{
uint16_t pdu_len = 0;
uint8_t data_register = 0;
struct dlmstp_user_data_t *port;
struct dlmstp_rs485_driver *driver;
uint16_t i;
uint32_t milliseconds;
uint32_t turnaround_milliseconds;
if (!MSTP_Port) {
return 0;
}
if (!MSTP_Port->UserData) {
return 0;
}
port = MSTP_Port->UserData;
if (!port) {
return 0;
}
driver = port->RS485_Driver;
if (!driver) {
return 0;
}
while (!MSTP_Port->InputBuffer) {
/* FIXME: develop configure an input buffer! */
}
if (driver->transmitting()) {
/* we're transmitting; do nothing else */
return 0;
}
/* only do receive state machine while we don't have a frame */
while ((MSTP_Port->ReceivedValidFrame == false) &&
(MSTP_Port->ReceivedValidFrameNotForUs == false) &&
(MSTP_Port->ReceivedInvalidFrame == false)) {
MSTP_Port->DataAvailable = driver->read(&data_register);
if (MSTP_Port->DataAvailable) {
MSTP_Port->DataRegister = data_register;
}
MSTP_Receive_Frame_FSM(MSTP_Port);
/* process another byte, if available */
if (!driver->read(NULL)) {
break;
}
}
if (MSTP_Port->ReceivedValidFrameNotForUs ||
MSTP_Port->ReceivedValidFrame || MSTP_Port->ReceivedInvalidFrame) {
/* delay after reception before transmitting - per MS/TP spec */
turnaround_milliseconds =
dlmstp_receive_turnaround_time(driver->baud_rate());
milliseconds = MSTP_Port->SilenceTimer(MSTP_Port);
if (milliseconds < turnaround_milliseconds) {
/* we're waiting; do nothing else */
return 0;
}
}
if (MSTP_Port->ReceivedValidFrameNotForUs) {
MSTP_Port->ReceivedValidFrameNotForUs = false;
port->Statistics.receive_valid_frame_counter++;
}
if (MSTP_Port->ReceivedValidFrame) {
port->Statistics.receive_valid_frame_counter++;
}
if (MSTP_Port->ReceivedInvalidFrame) {
port->Statistics.receive_invalid_frame_counter++;
}
if (MSTP_Port->receive_state == MSTP_RECEIVE_STATE_IDLE) {
/* only do master state machine while rx is idle */
if (MSTP_Port->This_Station <= DEFAULT_MAX_MASTER) {
while (MSTP_Master_Node_FSM(MSTP_Port)) {
/* do nothing while some states fast transition */
};
}
}
/* see if there is a packet available */
if (port->ReceivePacketPending) {
port->ReceivePacketPending = false;
port->Statistics.receive_pdu_counter++;
pdu_len = MSTP_Port->DataLength;
if (pdu_len > max_pdu) {
/* PDU is too large */
return 0;
}
if (!pdu) {
/* no place to put a PDU */
return 0;
}
/* copy input buffer to PDU */
for (i = 0; i < pdu_len; i++) {
pdu[i] = MSTP_Port->InputBuffer[i];
}
if (!src) {
/* no place to put a source address */
return 0;
}
/* copy source address */
src->len = 0;
src->net = 0;
src->mac_len = 1;
src->mac[0] = MSTP_Port->SourceAddress;
}
return pdu_len;
}
/**
* @brief fill a BACNET_ADDRESS with the MSTP MAC address
* @param src - a #BACNET_ADDRESS structure
* @param mstp_address - a BACnet MSTP address
*/
void dlmstp_fill_bacnet_address(BACNET_ADDRESS *src, uint8_t mstp_address)
{
int i = 0;
if (mstp_address == MSTP_BROADCAST_ADDRESS) {
/* mac_len = 0 if broadcast address */
src->mac_len = 0;
src->mac[0] = 0;
} else {
src->mac_len = 1;
src->mac[0] = mstp_address;
}
/* fill with 0's starting with index 1; index 0 filled above */
for (i = 1; i < MAX_MAC_LEN; i++) {
src->mac[i] = 0;
}
src->net = 0;
src->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
src->adr[i] = 0;
}
}
/**
* @brief Set the MSTP MAC address
* @param mac_address - MAC address to set
*/
void dlmstp_set_mac_address(uint8_t mac_address)
{
/* Master Nodes can only have address 0-127 */
if (mac_address <= 127) {
if (MSTP_Port) {
MSTP_Port->This_Station = mac_address;
}
}
return;
}
/**
* @brief Get the MSTP MAC address
* @return MSTP MAC address
*/
uint8_t dlmstp_mac_address(void)
{
uint8_t value = 0;
if (MSTP_Port) {
value = MSTP_Port->This_Station;
}
return value;
}
/**
* @brief Set the Max_Info_Frames parameter value
*
* @note This parameter represents the value of the Max_Info_Frames property
* of the node's Device object. The value of Max_Info_Frames specifies the
* maximum number of information frames the node may send before it must
* pass the token. Max_Info_Frames may have different values on different
* nodes. This may be used to allocate more or less of the available link
* bandwidth to particular nodes. If Max_Info_Frames is not writable in a
* node, its value shall be 1.
*
* @param max_info_frames - parameter value to set
*/
void dlmstp_set_max_info_frames(uint8_t max_info_frames)
{
if (max_info_frames >= 1) {
if (MSTP_Port) {
MSTP_Port->Nmax_info_frames = max_info_frames;
}
}
return;
}
/**
* @brief Get the MSTP max-info-frames value
* @return the MSTP max-info-frames value
*/
uint8_t dlmstp_max_info_frames(void)
{
uint8_t value = 0;
if (MSTP_Port) {
value = MSTP_Port->Nmax_info_frames;
}
return value;
}
/**
* @brief Set the Max_Master property value for this MSTP datalink
*
* @note This parameter represents the value of the Max_Master property of
* the node's Device object. The value of Max_Master specifies the highest
* allowable address for master nodes. The value of Max_Master shall be
* less than or equal to 127. If Max_Master is not writable in a node,
* its value shall be 127.
*
* @param max_master - value to be set
*/
void dlmstp_set_max_master(uint8_t max_master)
{
if (max_master <= 127) {
if (MSTP_Port->This_Station <= max_master) {
MSTP_Port->Nmax_master = max_master;
}
}
return;
}
/**
* @brief Get the largest peer MAC address that we will seek
* @return largest peer MAC address
*/
uint8_t dlmstp_max_master(void)
{
uint8_t value = 0;
if (MSTP_Port) {
value = MSTP_Port->Nmax_master;
}
return value;
}
/**
* @brief Initialize the data link broadcast address
* @param my_address - address to be filled with unicast designator
*/
void dlmstp_get_my_address(BACNET_ADDRESS *my_address)
{
int i = 0; /* counter */
my_address->mac_len = 1;
if (MSTP_Port) {
my_address->mac[0] = MSTP_Port->This_Station;
}
my_address->net = 0; /* local only, no routing */
my_address->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
my_address->adr[i] = 0;
}
return;
}
/**
* @brief Initialize the a data link broadcast address
* @param dest - address to be filled with broadcast designator
*/
void dlmstp_get_broadcast_address(BACNET_ADDRESS *dest)
{ /* destination address */
int i = 0; /* counter */
if (dest) {
dest->mac_len = 1;
dest->mac[0] = MSTP_BROADCAST_ADDRESS;
dest->net = BACNET_BROADCAST_NETWORK;
dest->len = 0; /* always zero when DNET is broadcast */
for (i = 0; i < MAX_MAC_LEN; i++) {
dest->adr[i] = 0;
}
}
return;
}
/**
* @brief Determine if the send PDU queue is empty
* @return true if the send PDU is empty
*/
bool dlmstp_send_pdu_queue_empty(void)
{
bool status = false;
struct dlmstp_user_data_t *port;
if (MSTP_Port) {
port = MSTP_Port->UserData;
if (port) {
status = Ringbuf_Empty(&port->PDU_Queue);
}
}
return status;
}
/**
* @brief Determine if the send PDU queue is full
* @return true if the send PDU is full
*/
bool dlmstp_send_pdu_queue_full(void)
{
bool status = false;
struct dlmstp_user_data_t *port;
if (MSTP_Port) {
port = MSTP_Port->UserData;
if (port) {
status = Ringbuf_Full(&port->PDU_Queue);
}
}
return status;
}
/**
* @brief Initialize the RS-485 baud rate
* @param baudrate - RS-485 baud rate in bits per second (bps)
* @return true if the baud rate was valid
*/
void dlmstp_set_baud_rate(uint32_t baud)
{
struct dlmstp_user_data_t *port;
struct dlmstp_rs485_driver *driver;
if (!MSTP_Port) {
return;
}
if (!MSTP_Port->UserData) {
return;
}
port = MSTP_Port->UserData;
driver = port->RS485_Driver;
if (!driver) {
return;
}
driver->baud_rate_set(baud);
}
/**
* @brief Return the RS-485 baud rate
* @return baud - RS-485 baud rate in bits per second (bps)
*/
uint32_t dlmstp_baud_rate(void)
{
struct dlmstp_user_data_t *port;
struct dlmstp_rs485_driver *driver;
if (!MSTP_Port) {
return 0;
}
if (!MSTP_Port->UserData) {
return 0;
}
port = MSTP_Port->UserData;
driver = port->RS485_Driver;
if (!driver) {
return 0;
}
return driver->baud_rate();
}
/**
* @brief Copy the MSTP port statistics if they exist
* @param statistics - MSTP port statistics
*/
void dlmstp_fill_statistics(struct dlmstp_statistics *statistics)
{
struct dlmstp_user_data_t *port;
if (!MSTP_Port) {
return;
}
if (!MSTP_Port->UserData) {
return;
}
port = MSTP_Port->UserData;
if (!port) {
return;
}
if (statistics) {
*statistics = port->Statistics;
}
}
/**
* @brief Get the MSTP port Max-Info-Frames limit
* @return Max-Info-Frames limit
*/
uint8_t dlmstp_max_info_frames_limit(void)
{
return DLMSTP_MAX_INFO_FRAMES;
}
/**
* @brief Get the MSTP port Max-Master limit
* @return Max-Master limit
*/
uint8_t dlmstp_max_master_limit(void)
{
return DLMSTP_MAX_MASTER;
}
/**
* @brief Return the RS-485 silence time in milliseconds
* @param arg - pointer to MSTP port structure
* @return silence time in milliseconds
*/
uint32_t dlmstp_silence_milliseconds(void *arg)
{
uint32_t milliseconds = 0;
struct mstp_port_struct_t *port = arg;
struct dlmstp_user_data_t *user = NULL;
struct dlmstp_rs485_driver *driver = NULL;
if (port) {
user = port->UserData;
}
if (user) {
driver = user->RS485_Driver;
}
if (driver) {
milliseconds = driver->silence_milliseconds();
}
return milliseconds;
}
/**
* @brief Reset the RS-485 silence time to zero
* @param arg - pointer to MSTP port structure
*/
void dlmstp_silence_reset(void *arg)
{
struct mstp_port_struct_t *port = arg;
struct dlmstp_user_data_t *user = NULL;
struct dlmstp_rs485_driver *driver = NULL;
if (port) {
user = port->UserData;
}
if (user) {
driver = user->RS485_Driver;
}
if (driver) {
driver->silence_reset();
}
}
/**
* @brief Initialize this MS/TP datalink
* @param ifname user data structure
* @return true if the MSTP datalink is initialized
*/
bool dlmstp_init(char *ifname)
{
struct dlmstp_user_data_t *user_data;
MSTP_Port = (struct mstp_port_struct_t *)ifname;
if (MSTP_Port) {
MSTP_Port->SilenceTimer = dlmstp_silence_milliseconds;
MSTP_Port->SilenceTimerReset = dlmstp_silence_reset;
user_data = (struct dlmstp_user_data_t *)MSTP_Port->UserData;
if (user_data && !user_data->Initialized) {
Ringbuf_Init(&user_data->PDU_Queue,
(volatile uint8_t *)user_data->PDU_Buffer,
sizeof(user_data->PDU_Buffer), DLMSTP_MAX_INFO_FRAMES);
MSTP_Init(MSTP_Port);
user_data->Initialized = true;
}
}
return true;
}
+62 -1
View File
@@ -24,10 +24,12 @@
#ifndef DLMSTP_H
#define DLMSTP_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "bacnet/bacnet_stack_exports.h"
#include "bacnet/basic/sys/ringbuf.h"
#include "bacnet/datalink/mstpdef.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
@@ -54,6 +56,58 @@ typedef struct dlmstp_statistics {
uint32_t lost_token_counter;
} DLMSTP_STATISTICS;
#ifndef DLMSTP_MAX_INFO_FRAMES
#define DLMSTP_MAX_INFO_FRAMES DEFAULT_MAX_INFO_FRAMES
#endif
#ifndef DLMSTP_MAX_MASTER
#define DLMSTP_MAX_MASTER DEFAULT_MAX_MASTER
#endif
#ifndef DLMSTP_BAUD_RATE_DEFAULT
#define DLMSTP_BAUD_RATE_DEFAULT 38400UL
#endif
/**
* The structure of RS485 driver for BACnet MS/TP
*/
struct dlmstp_rs485_driver {
/** Initialize the driver hardware */
void (*init)(void);
/** Prepare & transmit a packet. */
void (*send)(uint8_t *payload, uint16_t payload_len);
/** Check if one received byte is available */
bool (*read)(uint8_t *buf);
/** true if the driver is transmitting */
bool (*transmitting)(void);
/** Get the current baud rate */
uint32_t (*baud_rate)(void);
/** Set the current baud rate */
bool (*baud_rate_set)(uint32_t baud);
/** Get the current silence time */
uint32_t (*silence_milliseconds)(void);
/** Reset the silence time */
void (*silence_reset)(void);
};
/**
* A structure of BACnet Port Data for BACnet MS/TP
*/
struct dlmstp_user_data_t {
struct dlmstp_statistics Statistics;
struct dlmstp_rs485_driver *RS485_Driver;
/* the PDU Queue is made of Nmax_info_frames x dlmstp_packet's */
RING_BUFFER PDU_Queue;
struct dlmstp_packet PDU_Buffer[DLMSTP_MAX_INFO_FRAMES];
bool Initialized;
bool ReceivePacketPending;
};
/* callback to signify the receipt of a preamble */
typedef void (*dlmstp_hook_frame_rx_start_cb)(void);
@@ -162,6 +216,13 @@ extern "C" {
BACNET_STACK_EXPORT
uint8_t dlmstp_max_master_limit(void);
BACNET_STACK_EXPORT
uint32_t dlmstp_silence_milliseconds(
void *arg);
BACNET_STACK_EXPORT
void dlmstp_silence_reset(
void *arg);
/* Set the callback function to be called on every valid received frame */
/* This is not necessary for normal usage, but is helpful if the caller */
/* needs to monitor traffic on the MS/TP bus */