From 442f408c1afd1831ee5af44396799ab8cf494180 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 27 May 2025 10:14:15 -0500 Subject: [PATCH] Bugfix/dlmstp api missing in ports os (#1003) * Added missing API defined in header into ports/win32/dlmstp.c module, added a PDU queue and refactored receive thread, and refactored MS/TP timing parameters. * Added missing API defined in header into ports/linux/dlmstp.c module, and refactored MS/TP timing parameters. * Added missing API defined in header into ports/bsd/dlmstp.c module, and refactored MS/TP timing parameters. * Reduce default MS/TP APDU to 480 to avoid extended frames by default. --- Makefile | 6 +- ports/bsd/dlmstp.c | 1028 ++++++++++++++++-------- ports/linux/dlmstp.c | 983 +++++++++++++++-------- ports/win32/datetime-init.c | 2 +- ports/win32/dlmstp.c | 1051 ++++++++++++++++--------- ports/win32/rs485.c | 16 +- src/bacnet/basic/server/bacnet_port.c | 3 + src/bacnet/config.h | 2 +- src/bacnet/datalink/dlenv.c | 71 +- src/bacnet/datalink/dlmstp.c | 7 +- 10 files changed, 2126 insertions(+), 1043 deletions(-) diff --git a/Makefile b/Makefile index 887b7888..f92d3da6 100644 --- a/Makefile +++ b/Makefile @@ -28,9 +28,13 @@ mingw32: export CC=$(ORIGINAL_CC) ; \ export LD=$(ORIGINAL_LD) +.PHONY: mstpwin32-clean +mstpwin32-clean: + $(MAKE) LEGACY=true BACDL=mstp BACNET_PORT=win32 -s -C apps clean + .PHONY: mstpwin32 mstpwin32: - $(MAKE) BACDL=mstp BACNET_PORT=win32 -s -C apps all + $(MAKE) LEGACY=true BACDL=mstp BACNET_PORT=win32 -s -C apps all .PHONY: mstp mstp: diff --git a/ports/bsd/dlmstp.c b/ports/bsd/dlmstp.c index 3534df0e..9c907072 100644 --- a/ports/bsd/dlmstp.c +++ b/ports/bsd/dlmstp.c @@ -22,13 +22,12 @@ #include "bacnet/datalink/dlmstp.h" #include "bacnet/basic/sys/ringbuf.h" #include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/mstimer.h" /* OS Specific include */ #include "bacport.h" /* port specific */ #include "rs485.h" -/* Number of MS/TP Packets Rx/Tx */ -uint16_t MSTP_Packets = 0; /* packet queues */ static DLMSTP_PACKET Receive_Packet; /* mechanism to wait for a packet */ @@ -41,8 +40,8 @@ static pthread_mutex_t Master_Done_Mutex; static pthread_mutex_t Ring_Buffer_Mutex; static pthread_mutex_t Thread_Mutex; static pthread_t hThread; -static bool run_thread; - +static struct timespec Clock_Get_Time_Start; +static bool Thread_Run; /* local MS/TP port data - shared with RS-485 */ static struct mstp_port_struct_t MSTP_Port; /* buffers needed by mstp port struct */ @@ -61,108 +60,23 @@ struct mstp_pdu_packet { #endif static struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT]; static RING_BUFFER PDU_Queue; -/* The minimum time without a DataAvailable or ReceiveError event */ -/* that a node must wait for a station to begin replying to a */ -/* confirmed request: 255 milliseconds. (Implementations may use */ -/* larger values for this timeout, not to exceed 300 milliseconds.) */ -static uint16_t Treply_timeout = 300; -/* The time without a DataAvailable or ReceiveError event that a node must */ -/* wait for a remote node to begin using a token or replying to a Poll For */ -/* Master frame: 20 milliseconds. (Implementations may use larger values for */ -/* this timeout, not to exceed 35 milliseconds.) */ -static uint8_t Tusage_timeout = 30; -/* Timer that indicates line silence - and functions */ - -static struct timespec start; +/* local timer for tracking silence on the wire */ +static struct mstimer Silence_Timer; +/* local timer for tracking the last valid frame on the wire */ +static struct mstimer Valid_Frame_Timer; +/* callbacks for monitoring */ +static dlmstp_hook_frame_rx_start_cb Preamble_Callback; +static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Rx_Callback; +static dlmstp_hook_frame_rx_complete_cb Invalid_Frame_Rx_Callback; +static DLMSTP_STATISTICS DLMSTP_Statistics; /** - * Calculate the time difference between two timespec values. - * - * @param l - The minued (time from which we subtract). - * @param r - The subtrahend (time that is being subtracted). - * - * @returns True if the difference is negative, otherwise 0. + * @brief Cleanup the MS/TP datalink */ -static int timespec_subtract( - struct timespec *result, const struct timespec *l, const struct timespec *r) -{ -#define NS_PER_S 1000000000 /* nano-seconds per second */ - struct timespec right = *r; - int secs; - - /* Perform the carry for the later subtraction by updating y. */ - if (l->tv_nsec < right.tv_nsec) { - secs = (right.tv_nsec - l->tv_nsec) / NS_PER_S + 1; - right.tv_nsec -= NS_PER_S * secs; - right.tv_sec += secs; - } - if (l->tv_nsec - right.tv_nsec > NS_PER_S) { - secs = (l->tv_nsec - right.tv_nsec) / NS_PER_S; - right.tv_nsec += NS_PER_S * secs; - right.tv_sec -= secs; - } - - /* Compute the time remaining. tv_nsec is certainly positive. */ - result->tv_sec = l->tv_sec - right.tv_sec; - result->tv_nsec = l->tv_nsec - right.tv_nsec; - - return l->tv_sec < right.tv_sec; -} - -/** - * Add a certain number of nanoseconds to the specified time. - * - * @param ts - The time to which to add to. - * @param ns - The number of nanoseconds to add. Allowed range - * is -NS_PER_S..NS_PER_S (i.e., plus minus one second). - */ -static void timespec_add_ns(struct timespec *ts, long ns) -{ - ts->tv_nsec += ns; - if (ts->tv_nsec > NS_PER_S) { - ts->tv_nsec -= NS_PER_S; - ts->tv_sec += 1; - } else if (ts->tv_nsec < 0) { - ts->tv_nsec += NS_PER_S; - ts->tv_sec -= 1; - } -} - -static uint32_t Timer_Silence(void *pArg) -{ - struct timespec now, diff; - int32_t res; - - (void)pArg; - clock_gettime(CLOCK_MONOTONIC, &now); - timespec_subtract(&diff, &now, &start); - res = ((diff.tv_sec) * 1000 + (diff.tv_nsec) / 1000000); - - return (res >= 0 ? res : 0); -} - -static void Timer_Silence_Reset(void *pArg) -{ - (void)pArg; - clock_gettime(CLOCK_MONOTONIC, &start); -} - -static void get_abstime(struct timespec *abstime, unsigned long milliseconds) -{ - clock_gettime(CLOCK_MONOTONIC, abstime); - if (milliseconds > 1000) { - fprintf( - stderr, "DLMSTP: limited timeout of %lums to 1000ms\n", - milliseconds); - milliseconds = 1000; - } - timespec_add_ns(abstime, 1000000 * milliseconds); -} - void dlmstp_cleanup(void) { pthread_mutex_lock(&Thread_Mutex); - run_thread = false; + Thread_Run = false; pthread_mutex_unlock(&Thread_Mutex); pthread_join(hThread, NULL); pthread_cond_destroy(&Received_Frame_Flag); @@ -174,13 +88,20 @@ void dlmstp_cleanup(void) pthread_mutex_destroy(&Ring_Buffer_Mutex); } -/* returns number of bytes sent on success, zero on failure */ +/** + * @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, /* destination address */ - BACNET_NPDU_DATA *npdu_data, /* network information */ - uint8_t *pdu, /* any data to be sent - may be null */ + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, unsigned pdu_len) -{ /* number of bytes of data */ +{ int bytes_sent = 0; struct mstp_pdu_packet *pkt; unsigned i = 0; @@ -207,161 +128,12 @@ int dlmstp_send_pdu( return bytes_sent; } -uint16_t dlmstp_receive( - BACNET_ADDRESS *src, /* source address */ - uint8_t *pdu, /* PDU data */ - uint16_t max_pdu, /* amount of space available in the PDU */ - unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; - struct timespec abstime; - - (void)max_pdu; - /* see if there is a packet available, and a place - to put the reply (if necessary) and process it */ - pthread_mutex_lock(&Receive_Packet_Mutex); - get_abstime(&abstime, timeout); - pthread_cond_timedwait( - &Receive_Packet_Flag, &Receive_Packet_Mutex, &abstime); - if (Receive_Packet.ready) { - if (Receive_Packet.pdu_len) { - MSTP_Packets++; - if (src) { - memmove( - src, &Receive_Packet.address, - sizeof(Receive_Packet.address)); - } - if (pdu) { - memmove(pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); - } - pdu_len = Receive_Packet.pdu_len; - } - Receive_Packet.ready = false; - } - pthread_mutex_unlock(&Receive_Packet_Mutex); - - return pdu_len; -} - -static void *dlmstp_master_fsm_task(void *pArg) -{ - uint32_t silence = 0; - bool run_master = false; - bool thread_alive = true; - bool run_loop; - - (void)pArg; - while (thread_alive) { - if (MSTP_Port.ReceivedValidFrame == false && - MSTP_Port.ReceivedInvalidFrame == false) { - RS485_Check_UART_Data(&MSTP_Port); - MSTP_Receive_Frame_FSM(&MSTP_Port); - } - if (MSTP_Port.ReceivedValidFrame || MSTP_Port.ReceivedInvalidFrame) { - run_master = true; - } else { - silence = MSTP_Port.SilenceTimer(&MSTP_Port); - switch (MSTP_Port.master_state) { - case MSTP_MASTER_STATE_IDLE: - if (silence >= Tno_token) { - run_master = true; - } - break; - case MSTP_MASTER_STATE_WAIT_FOR_REPLY: - if (silence >= Treply_timeout) { - run_master = true; - } - break; - case MSTP_MASTER_STATE_POLL_FOR_MASTER: - if (silence >= Tusage_timeout) { - run_master = true; - } - break; - default: - run_master = true; - break; - } - } - if (run_master) { - if (MSTP_Port.This_Station <= 127) { - run_loop = true; - while (run_loop) { - /* do nothing while immediate transitioning */ - run_loop = MSTP_Master_Node_FSM(&MSTP_Port); - pthread_mutex_lock(&Thread_Mutex); - if (!run_thread) { - run_loop = false; - } - pthread_mutex_unlock(&Thread_Mutex); - } - } else if (MSTP_Port.This_Station < 255) { - MSTP_Slave_Node_FSM(&MSTP_Port); - } - } - pthread_mutex_lock(&Thread_Mutex); - thread_alive = run_thread; - pthread_mutex_unlock(&Thread_Mutex); - } - - return NULL; -} - -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; - } -} - -/* for the MS/TP state machine to use for putting received data */ -uint16_t MSTP_Put_Receive(struct mstp_port_struct_t *mstp_port) -{ - uint16_t pdu_len = 0; - - pthread_mutex_lock(&Receive_Packet_Mutex); - if (Receive_Packet.ready) { - debug_printf("MS/TP: Dropped! Not Ready.\n"); - } else { - /* bounds check - maybe this should send an abort? */ - pdu_len = mstp_port->DataLength; - if (pdu_len > sizeof(Receive_Packet.pdu)) { - pdu_len = sizeof(Receive_Packet.pdu); - } - if (pdu_len == 0) { - debug_printf("MS/TP: PDU Length is 0!\n"); - } - memmove( - (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], - pdu_len); - dlmstp_fill_bacnet_address( - &Receive_Packet.address, mstp_port->SourceAddress); - Receive_Packet.pdu_len = mstp_port->DataLength; - Receive_Packet.ready = true; - pthread_cond_signal(&Receive_Packet_Flag); - } - pthread_mutex_unlock(&Receive_Packet_Mutex); - - return pdu_len; -} - -/* for the MS/TP state machine to use for getting data to send */ -/* Return: amount of PDU data */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) { /* milliseconds to wait for a packet */ uint16_t pdu_len = 0; @@ -392,19 +164,15 @@ uint16_t MSTP_Get_Send(struct mstp_port_struct_t *mstp_port, unsigned timeout) } /** - * @brief Send an MSTP frame - * @param mstp_port - port specific data - * @param buffer - data to send - * @param nbytes - number of bytes of data to send + * @brief Determine if the reply packet is the data expected + * @param request_pdu - PDU of the data + * @param request_pdu_len - number of bytes of PDU data + * @param src_address - source address of the request + * @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 */ -void MSTP_Send_Frame( - struct mstp_port_struct_t *mstp_port, - const uint8_t *buffer, - uint16_t nbytes) -{ - RS485_Send_Frame(mstp_port, buffer, nbytes); -} - static bool dlmstp_compare_data_expecting_reply( const uint8_t *request_pdu, uint16_t request_pdu_len, @@ -434,18 +202,20 @@ static bool dlmstp_compare_data_expecting_reply( /* decode the request data */ request.address.mac[0] = src_address; request.address.mac_len = 1; - offset = bacnet_npdu_decode( + offset = (uint16_t)bacnet_npdu_decode( request_pdu, request_pdu_len, NULL, &request.address, &request.npdu_data); if (request.npdu_data.network_layer_message) { - debug_printf("DLMSTP: DER Compare failed: " - "Request is Network message.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Request is Network message.\n"); return false; } request.pdu_type = request_pdu[offset] & 0xF0; if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) { - debug_printf("DLMSTP: DER Compare failed: " - "Not Confirmed Request.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Not Confirmed Request.\n"); return false; } request.invoke_id = request_pdu[offset + 2]; @@ -458,11 +228,12 @@ static bool dlmstp_compare_data_expecting_reply( /* decode the reply data */ reply.address.mac[0] = dest_address; reply.address.mac_len = 1; - offset = bacnet_npdu_decode( + offset = (uint16_t)bacnet_npdu_decode( reply_pdu, reply_pdu_len, &reply.address, NULL, &reply.npdu_data); if (reply.npdu_data.network_layer_message) { - debug_printf("DLMSTP: DER Compare failed: " - "Reply is Network message.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Reply is Network message.\n"); return false; } /* reply could be a lot of things: @@ -499,26 +270,30 @@ static bool dlmstp_compare_data_expecting_reply( (reply.pdu_type == PDU_TYPE_ABORT) || (reply.pdu_type == PDU_TYPE_SEGMENT_ACK)) { if (request.invoke_id != reply.invoke_id) { - debug_printf("DLMSTP: DER Compare failed: " - "Invoke ID mismatch.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Invoke ID mismatch.\n"); return false; } } else { if (request.invoke_id != reply.invoke_id) { - debug_printf("DLMSTP: DER Compare failed: " - "Invoke ID mismatch.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Invoke ID mismatch.\n"); return false; } if (request.service_choice != reply.service_choice) { - debug_printf("DLMSTP: DER Compare failed: " - "Service choice mismatch.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "Service choice mismatch.\n"); return false; } } if (request.npdu_data.protocol_version != reply.npdu_data.protocol_version) { - debug_printf("DLMSTP: DER Compare failed: " - "NPDU Protocol Version mismatch.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "NPDU Protocol Version mismatch.\n"); return false; } #if 0 @@ -531,18 +306,25 @@ static bool dlmstp_compare_data_expecting_reply( } #endif if (!bacnet_address_same(&request.address, &reply.address)) { - debug_printf("DLMSTP: DER Compare failed: " - "BACnet Address mismatch.\n"); + debug_printf( + "DLMSTP: DER Compare failed: " + "BACnet Address mismatch.\n"); return false; } return true; } -/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; /* return value */ +{ + uint16_t pdu_len = 0; bool matched = false; uint8_t frame_type = 0; struct mstp_pdu_packet *pkt; @@ -570,36 +352,309 @@ uint16_t MSTP_Get_Reply(struct mstp_port_struct_t *mstp_port, unsigned timeout) &mstp_port->OutputBuffer[0], /* <-- loading this */ mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + DLMSTP_Statistics.transmit_pdu_counter++; (void)Ringbuf_Pop(&PDU_Queue, NULL); return pdu_len; } -void dlmstp_set_mac_address(uint8_t mac_address) +/** + * @brief Send an MSTP frame + * @param mstp_port - port specific data + * @param buffer - data to send + * @param nbytes - number of bytes of data to send + */ +void MSTP_Send_Frame( + struct mstp_port_struct_t *mstp_port, + const uint8_t *buffer, + uint16_t nbytes) { - /* Master Nodes can only have address 0-127 */ - if (mac_address <= 127) { - MSTP_Port.This_Station = mac_address; - if (mac_address > MSTP_Port.Nmax_master) { - dlmstp_set_max_master(mac_address); - } - } - - return; + RS485_Send_Frame(mstp_port, buffer, nbytes); + DLMSTP_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(struct mstp_port_struct_t *mstp_port) +{ + uint16_t pdu_len = 0; + + pthread_mutex_lock(&Receive_Packet_Mutex); + if (Receive_Packet.ready) { + debug_printf("MS/TP: Dropped! Not Ready.\n"); + } else { + /* bounds check - maybe this should send an abort? */ + pdu_len = mstp_port->DataLength; + if (pdu_len > sizeof(Receive_Packet.pdu)) { + pdu_len = sizeof(Receive_Packet.pdu); + } + if (pdu_len == 0) { + debug_printf("MS/TP: PDU Length is 0!\n"); + } + memmove( + (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], + pdu_len); + dlmstp_fill_bacnet_address( + &Receive_Packet.address, mstp_port->SourceAddress); + Receive_Packet.pdu_len = mstp_port->DataLength; + Receive_Packet.ready = true; + pthread_cond_signal(&Receive_Packet_Flag); + } + pthread_mutex_unlock(&Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * Add a certain number of nanoseconds to the specified time. + * + * @param ts - The time to which to add to. + * @param ns - The number of nanoseconds to add. Allowed range + * is -NS_PER_S..NS_PER_S (i.e., plus minus one second). + */ +static void timespec_add_ns(struct timespec *ts, long ns) +{ + /* nano-seconds per second */ + const long NS_PER_S = 1000000000L; + + ts->tv_nsec += ns; + if (ts->tv_nsec > NS_PER_S) { + ts->tv_nsec -= NS_PER_S; + ts->tv_sec += 1; + } else if (ts->tv_nsec < 0) { + ts->tv_nsec += NS_PER_S; + ts->tv_sec -= 1; + } +} + +/** + * @brief Get abstime for use in thread + * @param abstime - place to put the absolute time + * @param milliseconds - number of milliseconds to add + */ +static void get_abstime(struct timespec *abstime, unsigned long milliseconds) +{ + clock_gettime(CLOCK_MONOTONIC, abstime); + if (milliseconds > 1000) { + fprintf( + stderr, "DLMSTP: limited timeout of %lums to 1000ms\n", + milliseconds); + milliseconds = 1000; + } + timespec_add_ns(abstime, 1000000 * milliseconds); +} + +/** + * @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, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; + struct timespec abstime; + + (void)max_pdu; + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + pthread_mutex_lock(&Receive_Packet_Mutex); + get_abstime(&abstime, timeout); + pthread_cond_timedwait( + &Receive_Packet_Flag, &Receive_Packet_Mutex, &abstime); + if (Receive_Packet.ready) { + if (Receive_Packet.pdu_len) { + DLMSTP_Statistics.receive_pdu_counter++; + if (src) { + memmove( + src, &Receive_Packet.address, + sizeof(Receive_Packet.address)); + } + if (pdu) { + memmove(pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); + } + pdu_len = Receive_Packet.pdu_len; + } + Receive_Packet.ready = false; + } + pthread_mutex_unlock(&Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * @brief Thread for the MS/TP state machines + * @param pArg not used + */ +static void *dlmstp_thread(void *pArg) +{ + uint32_t silence_milliseconds = 0; + bool run_master = false; + bool thread_alive = true; + bool run_loop; + MSTP_MASTER_STATE master_state; + + (void)pArg; + while (thread_alive) { + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE) { + if (Preamble_Callback) { + Preamble_Callback(); + } + } + } + if (MSTP_Port.ReceivedValidFrame) { + DLMSTP_Statistics.receive_valid_frame_counter++; + if (Valid_Frame_Rx_Callback) { + Valid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else if (MSTP_Port.ReceivedInvalidFrame) { + if (Invalid_Frame_Rx_Callback) { + DLMSTP_Statistics.receive_invalid_frame_counter++; + Invalid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else { + silence_milliseconds = MSTP_Port.SilenceTimer(&MSTP_Port); + switch (MSTP_Port.master_state) { + case MSTP_MASTER_STATE_IDLE: + if (silence_milliseconds >= Tno_token) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (silence_milliseconds >= MSTP_Port.Treply_timeout) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (silence_milliseconds >= MSTP_Port.Tusage_timeout) { + run_master = true; + } + break; + default: + run_master = true; + break; + } + } + if (run_master) { + run_master = false; + if (MSTP_Port.SlaveNodeEnabled) { + MSTP_Slave_Node_FSM(&MSTP_Port); + } else { + if (MSTP_Port.ZeroConfigEnabled || MSTP_Port.CheckAutoBaud) { + /* if we are in auto baud or zero config mode, + we need to run the master state machine */ + } else if (MSTP_Port.This_Station > DEFAULT_MAX_MASTER) { + /* Master node address must be restricted */ + continue; + } + master_state = MSTP_Port.master_state; + run_loop = true; + while (run_loop) { + /* wait while some states fast transition */ + run_loop = MSTP_Master_Node_FSM(&MSTP_Port); + if (master_state != MSTP_Port.master_state) { + if (MSTP_Port.master_state == + MSTP_MASTER_STATE_NO_TOKEN) { + DLMSTP_Statistics.lost_token_counter++; + } + master_state = MSTP_Port.master_state; + } + pthread_mutex_lock(&Thread_Mutex); + if (!Thread_Run) { + run_loop = false; + } + pthread_mutex_unlock(&Thread_Mutex); + } + } + } + pthread_mutex_lock(&Thread_Mutex); + thread_alive = Thread_Run; + pthread_mutex_unlock(&Thread_Mutex); + } + + return NULL; +} + +/** + * @brief Fill a BACnet address with the MSTP address + * @param src the BACnet address to fill + * @param mstp_address the MSTP MAC 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) +{ + MSTP_Port.This_Station = mac_address; +} + +/** + * @brief Get the MSTP MAC address + * @return MSTP MAC address + */ uint8_t dlmstp_mac_address(void) { return MSTP_Port.This_Station; } -/* 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. */ +/** + * @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) { @@ -609,43 +664,48 @@ void dlmstp_set_max_info_frames(uint8_t 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) { return MSTP_Port.Nmax_info_frames; } -/* 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. */ +/** + * @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; - } + 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) { return MSTP_Port.Nmax_master; } -/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ -void dlmstp_set_baud_rate(uint32_t baud) -{ - RS485_Set_Baud_Rate(baud); -} - -uint32_t dlmstp_baud_rate(void) -{ - return RS485_Get_Baud_Rate(); -} - +/** + * @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 */ @@ -661,6 +721,10 @@ void dlmstp_get_my_address(BACNET_ADDRESS *my_address) 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 */ @@ -678,6 +742,249 @@ void dlmstp_get_broadcast_address(BACNET_ADDRESS *dest) return; } +/** + * @brief Get the MSTP port SoleMaster status + * @return true if the MSTP port is the SoleMaster + */ +bool dlmstp_sole_master(void) +{ + return MSTP_Port.SoleMaster; +} + +/** + * @brief Get the MSTP port SlaveNodeEnabled status + * @return true if the MSTP port has SlaveNodeEnabled + */ +bool dlmstp_slave_mode_enabled(void) +{ + return MSTP_Port.SlaveNodeEnabled; +} + +/** + * @brief Set the MSTP port SlaveNodeEnabled flag + * @param flag - true if the MSTP port has SlaveNodeEnabled + * @return true if the MSTP port SlaveNodeEnabled was set + * @note This flag is used to enable the Slave Node state machine + * for the MSTP port. The Slave Node state machine is used to + * respond to requests from the Master Node. + */ +bool dlmstp_slave_mode_enabled_set(bool flag) +{ + MSTP_Port.SlaveNodeEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port ZeroConfigEnabled status + * @return true if the MSTP port has ZeroConfigEnabled + */ +bool dlmstp_zero_config_enabled(void) +{ + return MSTP_Port.ZeroConfigEnabled; +} + +/** + * @brief Set the MSTP port ZeroConfigEnabled flag + * @param flag - true if the MSTP port has ZeroConfigEnabled + * @return true if the MSTP port ZeroConfigEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_zero_config_enabled_set(bool flag) +{ + MSTP_Port.ZeroConfigEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port AutoBaudEnabled status + * @return true if the MSTP port has AutoBaudEnabled + */ +bool dlmstp_check_auto_baud(void) +{ + return MSTP_Port.CheckAutoBaud; +} + +/** + * @brief Set the MSTP port AutoBaudEnabled flag + * @param flag - true if the MSTP port has AutoBaudEnabled + * @return true if the MSTP port AutoBaudEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_check_auto_baud_set(bool flag) +{ + MSTP_Port.CheckAutoBaud = flag; + if (flag) { + MSTP_Port.Auto_Baud_State = MSTP_AUTO_BAUD_STATE_INIT; + } + + return true; +} + +/** + * @brief Get the MSTP port MAC address that this node prefers to use. + * @return ZeroConfigStation value, or an out-of-range value if invalid + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +uint8_t dlmstp_zero_config_preferred_station(void) +{ + return MSTP_Port.Zero_Config_Preferred_Station; +} + +/** + * @brief Set the MSTP port MAC address that this node prefers to use. + * @param station - Zero_Config_Preferred_Station value + * @return true if the MSTP port Zero_Config_Preferred_Station was set + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +bool dlmstp_zero_config_preferred_station_set(uint8_t station) +{ + MSTP_Port.Zero_Config_Preferred_Station = station; + + return true; +} + +/** + * @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) +{ + RS485_Set_Baud_Rate(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) +{ + return RS485_Get_Baud_Rate(); +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Valid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_invalid_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Invalid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Preamble callback + * @param cb_func - callback function to be called when a preamble is received + */ +void dlmstp_set_frame_rx_start_callback(dlmstp_hook_frame_rx_start_cb cb_func) +{ + Preamble_Callback = cb_func; +} + +/** + * @brief Reset the MS/TP statistics + */ +void dlmstp_reset_statistics(void) +{ + memset(&DLMSTP_Statistics, 0, sizeof(struct dlmstp_statistics)); +} + +/** + * @brief Copy the MSTP port statistics if they exist + * @param statistics - MSTP port statistics + */ +void dlmstp_fill_statistics(struct dlmstp_statistics *statistics) +{ + if (statistics == NULL) { + return; + } + memmove(statistics, &DLMSTP_Statistics, sizeof(struct dlmstp_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) +{ + (void)arg; + return mstimer_elapsed(&Silence_Timer); +} + +/** + * @brief Return the valid frame time in milliseconds + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +uint32_t dlmstp_valid_frame_milliseconds(void *arg) +{ + (void)arg; + return mstimer_elapsed(&Valid_Frame_Timer); +} + +/** + * @brief Reset the valid frame timer + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +void dlmstp_valid_frame_milliseconds_reset(void *arg) +{ + (void)arg; + mstimer_restart(&Valid_Frame_Timer); +} + +/** + * @brief Reset the RS-485 silence time to zero + * @param arg - pointer to MSTP port structure + */ +void dlmstp_silence_reset(void *arg) +{ + (void)arg; + mstimer_set(&Silence_Timer, 0); +} + +/** + * @brief Initialize this MS/TP datalink + * @param ifname user data structure + * @return true if the MSTP datalink is initialized + */ bool dlmstp_init(char *ifname) { pthread_condattr_t attr; @@ -685,10 +992,20 @@ bool dlmstp_init(char *ifname) pthread_condattr_init(&attr); //TODO use mach_absolute_time() for MONOTONIC clock - - pthread_mutex_init(&Ring_Buffer_Mutex, NULL); + if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) { + fprintf( + stderr, "MS/TP Interface: %s\n failed to set MONOTONIC clock\n", + ifname); + exit(1); + } pthread_mutex_init(&Thread_Mutex, NULL); - + rv = pthread_mutex_init(&Ring_Buffer_Mutex, NULL); + if (rv != 0) { + fprintf( + stderr, "MS/TP Interface: %s\n cannot allocate PThread Mutex.\n", + ifname); + exit(1); + } /* initialize PDU queue */ Ringbuf_Init( &PDU_Queue, (uint8_t *)&PDU_Buffer, sizeof(struct mstp_pdu_packet), @@ -711,7 +1028,9 @@ bool dlmstp_init(char *ifname) ifname); exit(1); } + 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); @@ -721,20 +1040,45 @@ bool dlmstp_init(char *ifname) MSTP_Port.InputBufferSize = sizeof(RxBuffer); MSTP_Port.OutputBuffer = &TxBuffer[0]; MSTP_Port.OutputBufferSize = sizeof(TxBuffer); - clock_gettime(CLOCK_MONOTONIC, &start); - MSTP_Port.SilenceTimer = Timer_Silence; - MSTP_Port.SilenceTimerReset = Timer_Silence_Reset; + MSTP_Port.SilenceTimer = dlmstp_silence_milliseconds; + MSTP_Port.SilenceTimerReset = dlmstp_silence_reset; + MSTP_Port.ValidFrameTimer = dlmstp_valid_frame_milliseconds; + MSTP_Port.ValidFrameTimerReset = dlmstp_valid_frame_milliseconds_reset; + MSTP_Port.BaudRate = dlmstp_baud_rate; + MSTP_Port.BaudRateSet = dlmstp_set_baud_rate; MSTP_Init(&MSTP_Port); - debug_fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station); - debug_fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master); - debug_fprintf( - stderr, "MS/TP Max_Info_Frames: %u\n", MSTP_Port.Nmax_info_frames); +#if PRINT_ENABLED + fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station); + fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master); + fprintf( + stderr, "MS/TP Max_Info_Frames: %u\n", + (unsigned)MSTP_Port.Nmax_info_frames); + fprintf( + stderr, "MS/TP RxBuf[%u] TxBuf[%u]\n", + (unsigned)MSTP_Port.InputBufferSize, + (unsigned)MSTP_Port.OutputBufferSize); + fprintf( + stderr, + "MS/TP SlaveModeEnabled" + ": %s\n", + (MSTP_Port.SlaveNodeEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP ZeroConfigEnabled" + ": %s\n", + (MSTP_Port.ZeroConfigEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP CheckAutoBaud" + ": %s\n", + (MSTP_Port.CheckAutoBaud ? "true" : "false")); fflush(stderr); +#endif /* start one thread */ - run_thread = true; - rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, NULL); + Thread_Run = true; + rv = pthread_create(&hThread, NULL, dlmstp_thread, NULL); if (rv != 0) { - fprintf(stderr, "Failed to start Master Node FSM task\n"); + fprintf(stderr, "Failed to start MS/TP thread\n"); } return true; diff --git a/ports/linux/dlmstp.c b/ports/linux/dlmstp.c index a16bc547..6be0eacb 100644 --- a/ports/linux/dlmstp.c +++ b/ports/linux/dlmstp.c @@ -22,14 +22,12 @@ #include "bacnet/datalink/dlmstp.h" #include "bacnet/basic/sys/ringbuf.h" #include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/mstimer.h" /* OS Specific include */ #include "bacport.h" /* port specific */ #include "rs485.h" -/* Number of MS/TP Packets Rx/Tx */ -uint16_t MSTP_Packets = 0; - /* packet queues */ static DLMSTP_PACKET Receive_Packet; /* mechanism to wait for a packet */ @@ -42,8 +40,8 @@ static pthread_mutex_t Master_Done_Mutex; static pthread_mutex_t Ring_Buffer_Mutex; static pthread_mutex_t Thread_Mutex; static pthread_t hThread; -static bool run_thread; - +static struct timespec Clock_Get_Time_Start; +static bool Thread_Run; /* local MS/TP port data - shared with RS-485 */ static struct mstp_port_struct_t MSTP_Port; /* buffers needed by mstp port struct */ @@ -62,108 +60,23 @@ struct mstp_pdu_packet { #endif static struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT]; static RING_BUFFER PDU_Queue; -/* The minimum time without a DataAvailable or ReceiveError event */ -/* that a node must wait for a station to begin replying to a */ -/* confirmed request: 255 milliseconds. (Implementations may use */ -/* larger values for this timeout, not to exceed 300 milliseconds.) */ -static uint16_t Treply_timeout = 300; -/* The time without a DataAvailable or ReceiveError event that a node must */ -/* wait for a remote node to begin using a token or replying to a Poll For */ -/* Master frame: 20 milliseconds. (Implementations may use larger values for */ -/* this timeout, not to exceed 35 milliseconds.) */ -static uint8_t Tusage_timeout = 30; -/* Timer that indicates line silence - and functions */ - -static struct timespec start; +/* local timer for tracking silence on the wire */ +static struct mstimer Silence_Timer; +/* local timer for tracking the last valid frame on the wire */ +static struct mstimer Valid_Frame_Timer; +/* callbacks for monitoring */ +static dlmstp_hook_frame_rx_start_cb Preamble_Callback; +static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Rx_Callback; +static dlmstp_hook_frame_rx_complete_cb Invalid_Frame_Rx_Callback; +static DLMSTP_STATISTICS DLMSTP_Statistics; /** - * Calculate the time difference between two timespec values. - * - * @param l - The minued (time from which we subtract). - * @param r - The subtrahend (time that is being subtracted). - * - * @returns True if the difference is negative, otherwise 0. + * @brief Cleanup the MS/TP datalink */ -static int timespec_subtract( - struct timespec *result, const struct timespec *l, const struct timespec *r) -{ -#define NS_PER_S 1000000000 /* nano-seconds per second */ - struct timespec right = *r; - int secs; - - /* Perform the carry for the later subtraction by updating y. */ - if (l->tv_nsec < right.tv_nsec) { - secs = (right.tv_nsec - l->tv_nsec) / NS_PER_S + 1; - right.tv_nsec -= NS_PER_S * secs; - right.tv_sec += secs; - } - if (l->tv_nsec - right.tv_nsec > NS_PER_S) { - secs = (l->tv_nsec - right.tv_nsec) / NS_PER_S; - right.tv_nsec += NS_PER_S * secs; - right.tv_sec -= secs; - } - - /* Compute the time remaining. tv_nsec is certainly positive. */ - result->tv_sec = l->tv_sec - right.tv_sec; - result->tv_nsec = l->tv_nsec - right.tv_nsec; - - return l->tv_sec < right.tv_sec; -} - -/** - * Add a certain number of nanoseconds to the specified time. - * - * @param ts - The time to which to add to. - * @param ns - The number of nanoseconds to add. Allowed range - * is -NS_PER_S..NS_PER_S (i.e., plus minus one second). - */ -static void timespec_add_ns(struct timespec *ts, long ns) -{ - ts->tv_nsec += ns; - if (ts->tv_nsec > NS_PER_S) { - ts->tv_nsec -= NS_PER_S; - ts->tv_sec += 1; - } else if (ts->tv_nsec < 0) { - ts->tv_nsec += NS_PER_S; - ts->tv_sec -= 1; - } -} - -static uint32_t Timer_Silence(void *pArg) -{ - struct timespec now, diff; - int32_t res; - - (void)pArg; - clock_gettime(CLOCK_MONOTONIC, &now); - timespec_subtract(&diff, &now, &start); - res = ((diff.tv_sec) * 1000 + (diff.tv_nsec) / 1000000); - - return (res >= 0 ? res : 0); -} - -static void Timer_Silence_Reset(void *pArg) -{ - (void)pArg; - clock_gettime(CLOCK_MONOTONIC, &start); -} - -static void get_abstime(struct timespec *abstime, unsigned long milliseconds) -{ - clock_gettime(CLOCK_MONOTONIC, abstime); - if (milliseconds > 1000) { - fprintf( - stderr, "DLMSTP: limited timeout of %lums to 1000ms\n", - milliseconds); - milliseconds = 1000; - } - timespec_add_ns(abstime, 1000000 * milliseconds); -} - void dlmstp_cleanup(void) { pthread_mutex_lock(&Thread_Mutex); - run_thread = false; + Thread_Run = false; pthread_mutex_unlock(&Thread_Mutex); pthread_join(hThread, NULL); pthread_cond_destroy(&Received_Frame_Flag); @@ -175,13 +88,20 @@ void dlmstp_cleanup(void) pthread_mutex_destroy(&Ring_Buffer_Mutex); } -/* returns number of bytes sent on success, zero on failure */ +/** + * @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, /* destination address */ - BACNET_NPDU_DATA *npdu_data, /* network information */ - uint8_t *pdu, /* any data to be sent - may be null */ + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, unsigned pdu_len) -{ /* number of bytes of data */ +{ int bytes_sent = 0; struct mstp_pdu_packet *pkt; unsigned i = 0; @@ -208,161 +128,12 @@ int dlmstp_send_pdu( return bytes_sent; } -uint16_t dlmstp_receive( - BACNET_ADDRESS *src, /* source address */ - uint8_t *pdu, /* PDU data */ - uint16_t max_pdu, /* amount of space available in the PDU */ - unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; - struct timespec abstime; - - (void)max_pdu; - /* see if there is a packet available, and a place - to put the reply (if necessary) and process it */ - pthread_mutex_lock(&Receive_Packet_Mutex); - get_abstime(&abstime, timeout); - pthread_cond_timedwait( - &Receive_Packet_Flag, &Receive_Packet_Mutex, &abstime); - if (Receive_Packet.ready) { - if (Receive_Packet.pdu_len) { - MSTP_Packets++; - if (src) { - memmove( - src, &Receive_Packet.address, - sizeof(Receive_Packet.address)); - } - if (pdu) { - memmove(pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); - } - pdu_len = Receive_Packet.pdu_len; - } - Receive_Packet.ready = false; - } - pthread_mutex_unlock(&Receive_Packet_Mutex); - - return pdu_len; -} - -static void *dlmstp_master_fsm_task(void *pArg) -{ - uint32_t silence = 0; - bool run_master = false; - bool thread_alive = true; - bool run_loop; - - (void)pArg; - while (thread_alive) { - if (MSTP_Port.ReceivedValidFrame == false && - MSTP_Port.ReceivedInvalidFrame == false) { - RS485_Check_UART_Data(&MSTP_Port); - MSTP_Receive_Frame_FSM(&MSTP_Port); - } - if (MSTP_Port.ReceivedValidFrame || MSTP_Port.ReceivedInvalidFrame) { - run_master = true; - } else { - silence = MSTP_Port.SilenceTimer(&MSTP_Port); - switch (MSTP_Port.master_state) { - case MSTP_MASTER_STATE_IDLE: - if (silence >= Tno_token) { - run_master = true; - } - break; - case MSTP_MASTER_STATE_WAIT_FOR_REPLY: - if (silence >= Treply_timeout) { - run_master = true; - } - break; - case MSTP_MASTER_STATE_POLL_FOR_MASTER: - if (silence >= Tusage_timeout) { - run_master = true; - } - break; - default: - run_master = true; - break; - } - } - if (run_master) { - if (MSTP_Port.This_Station <= 127) { - run_loop = true; - while (run_loop) { - /* do nothing while immediate transitioning */ - run_loop = MSTP_Master_Node_FSM(&MSTP_Port); - pthread_mutex_lock(&Thread_Mutex); - if (!run_thread) { - run_loop = false; - } - pthread_mutex_unlock(&Thread_Mutex); - } - } else if (MSTP_Port.This_Station < 255) { - MSTP_Slave_Node_FSM(&MSTP_Port); - } - } - pthread_mutex_lock(&Thread_Mutex); - thread_alive = run_thread; - pthread_mutex_unlock(&Thread_Mutex); - } - - return NULL; -} - -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; - } -} - -/* for the MS/TP state machine to use for putting received data */ -uint16_t MSTP_Put_Receive(struct mstp_port_struct_t *mstp_port) -{ - uint16_t pdu_len = 0; - - pthread_mutex_lock(&Receive_Packet_Mutex); - if (Receive_Packet.ready) { - debug_printf("MS/TP: Dropped! Not Ready.\n"); - } else { - /* bounds check - maybe this should send an abort? */ - pdu_len = mstp_port->DataLength; - if (pdu_len > sizeof(Receive_Packet.pdu)) { - pdu_len = sizeof(Receive_Packet.pdu); - } - if (pdu_len == 0) { - debug_printf("MS/TP: PDU Length is 0!\n"); - } - memmove( - (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], - pdu_len); - dlmstp_fill_bacnet_address( - &Receive_Packet.address, mstp_port->SourceAddress); - Receive_Packet.pdu_len = mstp_port->DataLength; - Receive_Packet.ready = true; - pthread_cond_signal(&Receive_Packet_Flag); - } - pthread_mutex_unlock(&Receive_Packet_Mutex); - - return pdu_len; -} - -/* for the MS/TP state machine to use for getting data to send */ -/* Return: amount of PDU data */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) { /* milliseconds to wait for a packet */ uint16_t pdu_len = 0; @@ -393,19 +164,15 @@ uint16_t MSTP_Get_Send(struct mstp_port_struct_t *mstp_port, unsigned timeout) } /** - * @brief Send an MSTP frame - * @param mstp_port - port specific data - * @param buffer - data to send - * @param nbytes - number of bytes of data to send + * @brief Determine if the reply packet is the data expected + * @param request_pdu - PDU of the data + * @param request_pdu_len - number of bytes of PDU data + * @param src_address - source address of the request + * @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 */ -void MSTP_Send_Frame( - struct mstp_port_struct_t *mstp_port, - const uint8_t *buffer, - uint16_t nbytes) -{ - RS485_Send_Frame(mstp_port, buffer, nbytes); -} - static bool dlmstp_compare_data_expecting_reply( const uint8_t *request_pdu, uint16_t request_pdu_len, @@ -435,7 +202,7 @@ static bool dlmstp_compare_data_expecting_reply( /* decode the request data */ request.address.mac[0] = src_address; request.address.mac_len = 1; - offset = bacnet_npdu_decode( + offset = (uint16_t)bacnet_npdu_decode( request_pdu, request_pdu_len, NULL, &request.address, &request.npdu_data); if (request.npdu_data.network_layer_message) { @@ -459,7 +226,7 @@ static bool dlmstp_compare_data_expecting_reply( /* decode the reply data */ reply.address.mac[0] = dest_address; reply.address.mac_len = 1; - offset = bacnet_npdu_decode( + offset = (uint16_t)bacnet_npdu_decode( reply_pdu, reply_pdu_len, &reply.address, NULL, &reply.npdu_data); if (reply.npdu_data.network_layer_message) { debug_printf("DLMSTP: DER Compare failed: " @@ -540,10 +307,16 @@ static bool dlmstp_compare_data_expecting_reply( return true; } -/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; /* return value */ +{ + uint16_t pdu_len = 0; bool matched = false; uint8_t frame_type = 0; struct mstp_pdu_packet *pkt; @@ -571,36 +344,309 @@ uint16_t MSTP_Get_Reply(struct mstp_port_struct_t *mstp_port, unsigned timeout) &mstp_port->OutputBuffer[0], /* <-- loading this */ mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + DLMSTP_Statistics.transmit_pdu_counter++; (void)Ringbuf_Pop(&PDU_Queue, NULL); return pdu_len; } -void dlmstp_set_mac_address(uint8_t mac_address) +/** + * @brief Send an MSTP frame + * @param mstp_port - port specific data + * @param buffer - data to send + * @param nbytes - number of bytes of data to send + */ +void MSTP_Send_Frame( + struct mstp_port_struct_t *mstp_port, + const uint8_t *buffer, + uint16_t nbytes) { - /* Master Nodes can only have address 0-127 */ - if (mac_address <= 127) { - MSTP_Port.This_Station = mac_address; - if (mac_address > MSTP_Port.Nmax_master) { - dlmstp_set_max_master(mac_address); - } - } - - return; + RS485_Send_Frame(mstp_port, buffer, nbytes); + DLMSTP_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(struct mstp_port_struct_t *mstp_port) +{ + uint16_t pdu_len = 0; + + pthread_mutex_lock(&Receive_Packet_Mutex); + if (Receive_Packet.ready) { + debug_printf("MS/TP: Dropped! Not Ready.\n"); + } else { + /* bounds check - maybe this should send an abort? */ + pdu_len = mstp_port->DataLength; + if (pdu_len > sizeof(Receive_Packet.pdu)) { + pdu_len = sizeof(Receive_Packet.pdu); + } + if (pdu_len == 0) { + debug_printf("MS/TP: PDU Length is 0!\n"); + } + memmove( + (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], + pdu_len); + dlmstp_fill_bacnet_address( + &Receive_Packet.address, mstp_port->SourceAddress); + Receive_Packet.pdu_len = mstp_port->DataLength; + Receive_Packet.ready = true; + pthread_cond_signal(&Receive_Packet_Flag); + } + pthread_mutex_unlock(&Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * Add a certain number of nanoseconds to the specified time. + * + * @param ts - The time to which to add to. + * @param ns - The number of nanoseconds to add. Allowed range + * is -NS_PER_S..NS_PER_S (i.e., plus minus one second). + */ +static void timespec_add_ns(struct timespec *ts, long ns) +{ + /* nano-seconds per second */ + const long NS_PER_S = 1000000000L; + + ts->tv_nsec += ns; + if (ts->tv_nsec > NS_PER_S) { + ts->tv_nsec -= NS_PER_S; + ts->tv_sec += 1; + } else if (ts->tv_nsec < 0) { + ts->tv_nsec += NS_PER_S; + ts->tv_sec -= 1; + } +} + +/** + * @brief Get abstime for use in thread + * @param abstime - place to put the absolute time + * @param milliseconds - number of milliseconds to add + */ +static void get_abstime(struct timespec *abstime, unsigned long milliseconds) +{ + clock_gettime(CLOCK_MONOTONIC, abstime); + if (milliseconds > 1000) { + fprintf( + stderr, "DLMSTP: limited timeout of %lums to 1000ms\n", + milliseconds); + milliseconds = 1000; + } + timespec_add_ns(abstime, 1000000 * milliseconds); +} + +/** + * @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, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; + struct timespec abstime; + + (void)max_pdu; + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + pthread_mutex_lock(&Receive_Packet_Mutex); + get_abstime(&abstime, timeout); + pthread_cond_timedwait( + &Receive_Packet_Flag, &Receive_Packet_Mutex, &abstime); + if (Receive_Packet.ready) { + if (Receive_Packet.pdu_len) { + DLMSTP_Statistics.receive_pdu_counter++; + if (src) { + memmove( + src, &Receive_Packet.address, + sizeof(Receive_Packet.address)); + } + if (pdu) { + memmove(pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); + } + pdu_len = Receive_Packet.pdu_len; + } + Receive_Packet.ready = false; + } + pthread_mutex_unlock(&Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * @brief Thread for the MS/TP state machines + * @param pArg not used + */ +static void *dlmstp_thread(void *pArg) +{ + uint32_t silence_milliseconds = 0; + bool run_master = false; + bool thread_alive = true; + bool run_loop; + MSTP_MASTER_STATE master_state; + + (void)pArg; + while (thread_alive) { + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE) { + if (Preamble_Callback) { + Preamble_Callback(); + } + } + } + if (MSTP_Port.ReceivedValidFrame) { + DLMSTP_Statistics.receive_valid_frame_counter++; + if (Valid_Frame_Rx_Callback) { + Valid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else if (MSTP_Port.ReceivedInvalidFrame) { + if (Invalid_Frame_Rx_Callback) { + DLMSTP_Statistics.receive_invalid_frame_counter++; + Invalid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else { + silence_milliseconds = MSTP_Port.SilenceTimer(&MSTP_Port); + switch (MSTP_Port.master_state) { + case MSTP_MASTER_STATE_IDLE: + if (silence_milliseconds >= Tno_token) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (silence_milliseconds >= MSTP_Port.Treply_timeout) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (silence_milliseconds >= MSTP_Port.Tusage_timeout) { + run_master = true; + } + break; + default: + run_master = true; + break; + } + } + if (run_master) { + run_master = false; + if (MSTP_Port.SlaveNodeEnabled) { + MSTP_Slave_Node_FSM(&MSTP_Port); + } else { + if (MSTP_Port.ZeroConfigEnabled || MSTP_Port.CheckAutoBaud) { + /* if we are in auto baud or zero config mode, + we need to run the master state machine */ + } else if (MSTP_Port.This_Station > DEFAULT_MAX_MASTER) { + /* Master node address must be restricted */ + continue; + } + master_state = MSTP_Port.master_state; + run_loop = true; + while (run_loop) { + /* wait while some states fast transition */ + run_loop = MSTP_Master_Node_FSM(&MSTP_Port); + if (master_state != MSTP_Port.master_state) { + if (MSTP_Port.master_state == + MSTP_MASTER_STATE_NO_TOKEN) { + DLMSTP_Statistics.lost_token_counter++; + } + master_state = MSTP_Port.master_state; + } + pthread_mutex_lock(&Thread_Mutex); + if (!Thread_Run) { + run_loop = false; + } + pthread_mutex_unlock(&Thread_Mutex); + } + } + } + pthread_mutex_lock(&Thread_Mutex); + thread_alive = Thread_Run; + pthread_mutex_unlock(&Thread_Mutex); + } + + return NULL; +} + +/** + * @brief Fill a BACnet address with the MSTP address + * @param src the BACnet address to fill + * @param mstp_address the MSTP MAC 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) +{ + MSTP_Port.This_Station = mac_address; +} + +/** + * @brief Get the MSTP MAC address + * @return MSTP MAC address + */ uint8_t dlmstp_mac_address(void) { return MSTP_Port.This_Station; } -/* 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. */ +/** + * @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) { @@ -610,43 +656,48 @@ void dlmstp_set_max_info_frames(uint8_t 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) { return MSTP_Port.Nmax_info_frames; } -/* 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. */ +/** + * @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; - } + 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) { return MSTP_Port.Nmax_master; } -/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ -void dlmstp_set_baud_rate(uint32_t baud) -{ - RS485_Set_Baud_Rate(baud); -} - -uint32_t dlmstp_baud_rate(void) -{ - return RS485_Get_Baud_Rate(); -} - +/** + * @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 */ @@ -662,6 +713,10 @@ void dlmstp_get_my_address(BACNET_ADDRESS *my_address) 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 */ @@ -679,6 +734,249 @@ void dlmstp_get_broadcast_address(BACNET_ADDRESS *dest) return; } +/** + * @brief Get the MSTP port SoleMaster status + * @return true if the MSTP port is the SoleMaster + */ +bool dlmstp_sole_master(void) +{ + return MSTP_Port.SoleMaster; +} + +/** + * @brief Get the MSTP port SlaveNodeEnabled status + * @return true if the MSTP port has SlaveNodeEnabled + */ +bool dlmstp_slave_mode_enabled(void) +{ + return MSTP_Port.SlaveNodeEnabled; +} + +/** + * @brief Set the MSTP port SlaveNodeEnabled flag + * @param flag - true if the MSTP port has SlaveNodeEnabled + * @return true if the MSTP port SlaveNodeEnabled was set + * @note This flag is used to enable the Slave Node state machine + * for the MSTP port. The Slave Node state machine is used to + * respond to requests from the Master Node. + */ +bool dlmstp_slave_mode_enabled_set(bool flag) +{ + MSTP_Port.SlaveNodeEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port ZeroConfigEnabled status + * @return true if the MSTP port has ZeroConfigEnabled + */ +bool dlmstp_zero_config_enabled(void) +{ + return MSTP_Port.ZeroConfigEnabled; +} + +/** + * @brief Set the MSTP port ZeroConfigEnabled flag + * @param flag - true if the MSTP port has ZeroConfigEnabled + * @return true if the MSTP port ZeroConfigEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_zero_config_enabled_set(bool flag) +{ + MSTP_Port.ZeroConfigEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port AutoBaudEnabled status + * @return true if the MSTP port has AutoBaudEnabled + */ +bool dlmstp_check_auto_baud(void) +{ + return MSTP_Port.CheckAutoBaud; +} + +/** + * @brief Set the MSTP port AutoBaudEnabled flag + * @param flag - true if the MSTP port has AutoBaudEnabled + * @return true if the MSTP port AutoBaudEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_check_auto_baud_set(bool flag) +{ + MSTP_Port.CheckAutoBaud = flag; + if (flag) { + MSTP_Port.Auto_Baud_State = MSTP_AUTO_BAUD_STATE_INIT; + } + + return true; +} + +/** + * @brief Get the MSTP port MAC address that this node prefers to use. + * @return ZeroConfigStation value, or an out-of-range value if invalid + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +uint8_t dlmstp_zero_config_preferred_station(void) +{ + return MSTP_Port.Zero_Config_Preferred_Station; +} + +/** + * @brief Set the MSTP port MAC address that this node prefers to use. + * @param station - Zero_Config_Preferred_Station value + * @return true if the MSTP port Zero_Config_Preferred_Station was set + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +bool dlmstp_zero_config_preferred_station_set(uint8_t station) +{ + MSTP_Port.Zero_Config_Preferred_Station = station; + + return true; +} + +/** + * @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) +{ + RS485_Set_Baud_Rate(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) +{ + return RS485_Get_Baud_Rate(); +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Valid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_invalid_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Invalid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Preamble callback + * @param cb_func - callback function to be called when a preamble is received + */ +void dlmstp_set_frame_rx_start_callback(dlmstp_hook_frame_rx_start_cb cb_func) +{ + Preamble_Callback = cb_func; +} + +/** + * @brief Reset the MS/TP statistics + */ +void dlmstp_reset_statistics(void) +{ + memset(&DLMSTP_Statistics, 0, sizeof(struct dlmstp_statistics)); +} + +/** + * @brief Copy the MSTP port statistics if they exist + * @param statistics - MSTP port statistics + */ +void dlmstp_fill_statistics(struct dlmstp_statistics *statistics) +{ + if (statistics == NULL) { + return; + } + memmove(statistics, &DLMSTP_Statistics, sizeof(struct dlmstp_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) +{ + (void)arg; + return mstimer_elapsed(&Silence_Timer); +} + +/** + * @brief Return the valid frame time in milliseconds + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +uint32_t dlmstp_valid_frame_milliseconds(void *arg) +{ + (void)arg; + return mstimer_elapsed(&Valid_Frame_Timer); +} + +/** + * @brief Reset the valid frame timer + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +void dlmstp_valid_frame_milliseconds_reset(void *arg) +{ + (void)arg; + mstimer_restart(&Valid_Frame_Timer); +} + +/** + * @brief Reset the RS-485 silence time to zero + * @param arg - pointer to MSTP port structure + */ +void dlmstp_silence_reset(void *arg) +{ + (void)arg; + mstimer_set(&Silence_Timer, 0); +} + +/** + * @brief Initialize this MS/TP datalink + * @param ifname user data structure + * @return true if the MSTP datalink is initialized + */ bool dlmstp_init(char *ifname) { pthread_condattr_t attr; @@ -691,10 +989,14 @@ bool dlmstp_init(char *ifname) ifname); exit(1); } - - pthread_mutex_init(&Ring_Buffer_Mutex, NULL); pthread_mutex_init(&Thread_Mutex, NULL); - + rv = pthread_mutex_init(&Ring_Buffer_Mutex, NULL); + if (rv != 0) { + fprintf( + stderr, "MS/TP Interface: %s\n cannot allocate PThread Mutex.\n", + ifname); + exit(1); + } /* initialize PDU queue */ Ringbuf_Init( &PDU_Queue, (uint8_t *)&PDU_Buffer, sizeof(struct mstp_pdu_packet), @@ -717,7 +1019,9 @@ bool dlmstp_init(char *ifname) ifname); exit(1); } + 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); @@ -727,20 +1031,45 @@ bool dlmstp_init(char *ifname) MSTP_Port.InputBufferSize = sizeof(RxBuffer); MSTP_Port.OutputBuffer = &TxBuffer[0]; MSTP_Port.OutputBufferSize = sizeof(TxBuffer); - clock_gettime(CLOCK_MONOTONIC, &start); - MSTP_Port.SilenceTimer = Timer_Silence; - MSTP_Port.SilenceTimerReset = Timer_Silence_Reset; + MSTP_Port.SilenceTimer = dlmstp_silence_milliseconds; + MSTP_Port.SilenceTimerReset = dlmstp_silence_reset; + MSTP_Port.ValidFrameTimer = dlmstp_valid_frame_milliseconds; + MSTP_Port.ValidFrameTimerReset = dlmstp_valid_frame_milliseconds_reset; + MSTP_Port.BaudRate = dlmstp_baud_rate; + MSTP_Port.BaudRateSet = dlmstp_set_baud_rate; MSTP_Init(&MSTP_Port); - debug_fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station); - debug_fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master); - debug_fprintf( - stderr, "MS/TP Max_Info_Frames: %u\n", MSTP_Port.Nmax_info_frames); +#if PRINT_ENABLED + fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station); + fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master); + fprintf( + stderr, "MS/TP Max_Info_Frames: %u\n", + (unsigned)MSTP_Port.Nmax_info_frames); + fprintf( + stderr, "MS/TP RxBuf[%u] TxBuf[%u]\n", + (unsigned)MSTP_Port.InputBufferSize, + (unsigned)MSTP_Port.OutputBufferSize); + fprintf( + stderr, + "MS/TP SlaveModeEnabled" + ": %s\n", + (MSTP_Port.SlaveNodeEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP ZeroConfigEnabled" + ": %s\n", + (MSTP_Port.ZeroConfigEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP CheckAutoBaud" + ": %s\n", + (MSTP_Port.CheckAutoBaud ? "true" : "false")); fflush(stderr); +#endif /* start one thread */ - run_thread = true; - rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, NULL); + Thread_Run = true; + rv = pthread_create(&hThread, NULL, dlmstp_thread, NULL); if (rv != 0) { - fprintf(stderr, "Failed to start Master Node FSM task\n"); + fprintf(stderr, "Failed to start MS/TP thread\n"); } return true; diff --git a/ports/win32/datetime-init.c b/ports/win32/datetime-init.c index 8eeb75d3..04384016 100644 --- a/ports/win32/datetime-init.c +++ b/ports/win32/datetime-init.c @@ -129,7 +129,7 @@ bool datetime_local( bool *dst_active) { bool status = false; - struct tm *tblock; + struct tm *tblock = NULL; #if defined(_MSC_VER) struct tm newtime = { 0 }; __time64_t long_time = 0; diff --git a/ports/win32/dlmstp.c b/ports/win32/dlmstp.c index d8c0ae23..5e25293f 100644 --- a/ports/win32/dlmstp.c +++ b/ports/win32/dlmstp.c @@ -1,301 +1,177 @@ -/************************************************************************** - * - * Copyright (C) 2006 Steve Karg - * - * SPDX-License-Identifier: MIT - * - *********************************************************************/ - +/** + * @file + * @author Steve Karg + * @date February 2006 + * @brief Implementation of the Network Layer using BACnet MS/TP transport + * @copyright SPDX-License-Identifier: MIT + * @defgroup DLMSTP BACnet MS/TP DataLink Network Layer + * @ingroup DataLink + */ #include #include #include #include #include -#include #include +#include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ #include "bacnet/bacaddr.h" #include "bacnet/npdu.h" -#include "bacnet/basic/sys/ringbuf.h" -#include "bacnet/basic/sys/mstimer.h" #include "bacnet/datalink/mstp.h" #include "bacnet/datalink/dlmstp.h" -/* OS common includes */ +#include "bacnet/basic/sys/ringbuf.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/mstimer.h" +/* OS Specific includes */ #include "bacport.h" /* port specific */ #include "rs485.h" -/* Number of MS/TP Packets Rx/Tx */ -uint16_t MSTP_Packets = 0; - /* packet queues */ static DLMSTP_PACKET Receive_Packet; +/* mechanism to wait for a packet */ static HANDLE Receive_Packet_Flag; -/* mechanism to wait for a frame in state machine */ -HANDLE Received_Frame_Flag; -static DLMSTP_PACKET Transmit_Packet; +static HANDLE Ring_Buffer_Mutex; +static HANDLE Receive_Packet_Mutex; /* local MS/TP port data - shared with RS-485 */ -struct mstp_port_struct_t MSTP_Port; +static struct mstp_port_struct_t MSTP_Port; /* buffers needed by mstp port struct */ static uint8_t TxBuffer[DLMSTP_MPDU_MAX]; static uint8_t RxBuffer[DLMSTP_MPDU_MAX]; -/* The minimum time without a DataAvailable or ReceiveError event */ -/* that a node must wait for a station to begin replying to a */ -/* confirmed request: 255 milliseconds. (Implementations may use */ -/* larger values for this timeout, not to exceed 300 milliseconds.) */ -static uint16_t Treply_timeout = 260; -/* The time without a DataAvailable or ReceiveError event that a node must */ -/* wait for a remote node to begin using a token or replying to a Poll For */ -/* Master frame: 20 milliseconds. (Implementations may use larger values for */ -/* this timeout, not to exceed 35 milliseconds.) */ -static uint8_t Tusage_timeout = 30; +/* data structure for MS/TP PDU Queue */ +struct mstp_pdu_packet { + bool data_expecting_reply; + uint8_t destination_mac; + uint16_t length; + uint8_t buffer[DLMSTP_MPDU_MAX]; +}; +/* count must be a power of 2 for ringbuf library */ +#ifndef MSTP_PDU_PACKET_COUNT +#define MSTP_PDU_PACKET_COUNT 8 +#endif +static struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT]; +static RING_BUFFER PDU_Queue; /* local timer for tracking silence on the wire */ static struct mstimer Silence_Timer; +/* local timer for tracking the last valid frame on the wire */ +static struct mstimer Valid_Frame_Timer; +/* callbacks for monitoring */ +static dlmstp_hook_frame_rx_start_cb Preamble_Callback; +static dlmstp_hook_frame_rx_complete_cb Valid_Frame_Rx_Callback; +static dlmstp_hook_frame_rx_complete_cb Invalid_Frame_Rx_Callback; +static DLMSTP_STATISTICS DLMSTP_Statistics; -/* Timer that indicates line silence - and functions */ -static uint32_t Timer_Silence(void *pArg) -{ - (void)pArg; - return mstimer_elapsed(&Silence_Timer); -} - -static void Timer_Silence_Reset(void *pArg) -{ - (void)pArg; - mstimer_set(&Silence_Timer, 0); -} - +/** + * @brief Cleanup the MS/TP datalink + */ void dlmstp_cleanup(void) { /* nothing to do for static buffers */ - if (Received_Frame_Flag) { - CloseHandle(Received_Frame_Flag); - } if (Receive_Packet_Flag) { CloseHandle(Receive_Packet_Flag); } + if (Ring_Buffer_Mutex) { + CloseHandle(Ring_Buffer_Mutex); + } + if (Receive_Packet_Mutex) { + CloseHandle(Receive_Packet_Mutex); + } } -/* returns number of bytes sent on success, zero on failure */ +/** + * @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, /* destination address */ - BACNET_NPDU_DATA *npdu_data, /* network information */ - uint8_t *pdu, /* any data to be sent - may be null */ + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, unsigned pdu_len) -{ /* number of bytes of data */ +{ int bytes_sent = 0; + struct mstp_pdu_packet *pkt; unsigned i = 0; - - if (!Transmit_Packet.ready) { - if (npdu_data->data_expecting_reply) { - Transmit_Packet.frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; - } else { - Transmit_Packet.frame_type = - FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; - } - Transmit_Packet.pdu_len = (uint16_t)pdu_len; + WaitForSingleObject(Ring_Buffer_Mutex, INFINITE); + pkt = (struct mstp_pdu_packet *)Ringbuf_Data_Peek(&PDU_Queue); + if (pkt) { + pkt->data_expecting_reply = npdu_data->data_expecting_reply; for (i = 0; i < pdu_len; i++) { - Transmit_Packet.pdu[i] = pdu[i]; + pkt->buffer[i] = pdu[i]; + } + pkt->length = pdu_len; + if (dest && dest->mac_len) { + pkt->destination_mac = dest->mac[0]; + } else { + /* mac_len = 0 is a broadcast address */ + pkt->destination_mac = MSTP_BROADCAST_ADDRESS; + } + if (Ringbuf_Data_Put(&PDU_Queue, (uint8_t *)pkt)) { + bytes_sent = pdu_len; } - bacnet_address_copy(&Transmit_Packet.address, dest); - bytes_sent = pdu_len + DLMSTP_HEADER_MAX; - Transmit_Packet.ready = true; } + ReleaseMutex(Ring_Buffer_Mutex); return bytes_sent; } -uint16_t dlmstp_receive( - BACNET_ADDRESS *src, /* source address */ - uint8_t *pdu, /* PDU data */ - uint16_t max_pdu, /* amount of space available in the PDU */ - unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; - DWORD wait_status = 0; - - (void)max_pdu; - /* see if there is a packet available, and a place - to put the reply (if necessary) and process it */ - wait_status = WaitForSingleObject(Receive_Packet_Flag, timeout); - if (wait_status == WAIT_OBJECT_0) { - if (Receive_Packet.ready) { - if (Receive_Packet.pdu_len) { - MSTP_Packets++; - if (src) { - memmove( - src, &Receive_Packet.address, - sizeof(Receive_Packet.address)); - } - if (pdu) { - memmove( - pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); - } - pdu_len = Receive_Packet.pdu_len; - } - Receive_Packet.ready = false; - } - } - - return pdu_len; -} - -static void dlmstp_receive_fsm_task(void *pArg) -{ - bool received_frame; - - (void)pArg; - (void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - for (;;) { - /* only do receive state machine while we don't have a frame */ - if ((MSTP_Port.ReceivedValidFrame == false) && - (MSTP_Port.ReceivedInvalidFrame == false)) { - do { - RS485_Check_UART_Data(&MSTP_Port); - MSTP_Receive_Frame_FSM(&MSTP_Port); - received_frame = MSTP_Port.ReceivedValidFrame || - MSTP_Port.ReceivedInvalidFrame; - if (received_frame) { - ReleaseSemaphore(Received_Frame_Flag, 1, NULL); - break; - } - } while (MSTP_Port.DataAvailable); - } - } -} - -static void dlmstp_master_fsm_task(void *pArg) -{ - DWORD dwMilliseconds = 0; - - (void)pArg; - (void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - for (;;) { - switch (MSTP_Port.master_state) { - case MSTP_MASTER_STATE_IDLE: - dwMilliseconds = Tno_token; - break; - case MSTP_MASTER_STATE_WAIT_FOR_REPLY: - dwMilliseconds = Treply_timeout; - break; - case MSTP_MASTER_STATE_POLL_FOR_MASTER: - dwMilliseconds = Tusage_timeout; - break; - default: - dwMilliseconds = 0; - break; - } - if (dwMilliseconds) { - WaitForSingleObject(Received_Frame_Flag, dwMilliseconds); - } - while (MSTP_Master_Node_FSM(&MSTP_Port)) - ; - } -} - -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; - } -} - -/* for the MS/TP state machine to use for putting received data */ -uint16_t MSTP_Put_Receive(struct mstp_port_struct_t *mstp_port) -{ - uint16_t pdu_len = 0; - BOOL rc; - - if (!Receive_Packet.ready) { - /* bounds check - maybe this should send an abort? */ - pdu_len = mstp_port->DataLength; - if (pdu_len > sizeof(Receive_Packet.pdu)) { - pdu_len = sizeof(Receive_Packet.pdu); - } - memmove( - (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], - pdu_len); - dlmstp_fill_bacnet_address( - &Receive_Packet.address, mstp_port->SourceAddress); - Receive_Packet.pdu_len = mstp_port->DataLength; - Receive_Packet.ready = true; - rc = ReleaseSemaphore(Receive_Packet_Flag, 1, NULL); - (void)rc; - } - - return pdu_len; -} - -/* for the MS/TP state machine to use for getting data to send */ -/* Return: amount of PDU data */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) { /* milliseconds to wait for a packet */ uint16_t pdu_len = 0; - uint8_t destination = 0; /* destination address */ + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; (void)timeout; - if (!Transmit_Packet.ready) { + WaitForSingleObject(Ring_Buffer_Mutex, INFINITE); + if (Ringbuf_Empty(&PDU_Queue)) { + ReleaseMutex(Ring_Buffer_Mutex); return 0; } - /* load destination MAC address */ - if (Transmit_Packet.address.mac_len) { - destination = Transmit_Packet.address.mac[0]; + pkt = (struct mstp_pdu_packet *)Ringbuf_Peek(&PDU_Queue); + if (pkt->data_expecting_reply) { + frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; } else { - destination = MSTP_BROADCAST_ADDRESS; - } - if ((DLMSTP_HEADER_MAX + Transmit_Packet.pdu_len) > DLMSTP_MPDU_MAX) { - return 0; + frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; } /* convert the PDU into the MSTP Frame */ pdu_len = MSTP_Create_Frame( &mstp_port->OutputBuffer[0], /* <-- loading this */ - mstp_port->OutputBufferSize, Transmit_Packet.frame_type, destination, - mstp_port->This_Station, &Transmit_Packet.pdu[0], - Transmit_Packet.pdu_len); - Transmit_Packet.ready = false; + mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + (void)Ringbuf_Pop(&PDU_Queue, NULL); + ReleaseMutex(Ring_Buffer_Mutex); return pdu_len; } /** - * @brief Send an MSTP frame - * @param mstp_port - port specific data - * @param buffer - data to send - * @param nbytes - number of bytes of data to send + * @brief Determine if the reply packet is the data expected + * @param request_pdu - PDU of the data + * @param request_pdu_len - number of bytes of PDU data + * @param src_address - source address of the request + * @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 */ -void MSTP_Send_Frame( - struct mstp_port_struct_t *mstp_port, - const uint8_t *buffer, - uint16_t nbytes) -{ - RS485_Send_Frame(mstp_port, buffer, nbytes); -} - static bool dlmstp_compare_data_expecting_reply( const uint8_t *request_pdu, uint16_t request_pdu_len, uint8_t src_address, const uint8_t *reply_pdu, uint16_t reply_pdu_len, - const BACNET_ADDRESS *dest_address) + uint8_t dest_address) { uint16_t offset; /* One way to check the message is to compare NPDU @@ -312,8 +188,9 @@ static bool dlmstp_compare_data_expecting_reply( struct DER_compare_t reply; /* unused parameters */ - request_pdu_len = request_pdu_len; - reply_pdu_len = reply_pdu_len; + (void)request_pdu_len; + (void)reply_pdu_len; + /* decode the request data */ request.address.mac[0] = src_address; request.address.mac_len = 1; @@ -335,7 +212,8 @@ static bool dlmstp_compare_data_expecting_reply( request.service_choice = request_pdu[offset + 3]; } /* decode the reply data */ - bacnet_address_copy(&reply.address, dest_address); + reply.address.mac[0] = dest_address; + reply.address.mac_len = 1; offset = (uint16_t)bacnet_npdu_decode( reply_pdu, reply_pdu_len, &reply.address, NULL, &reply.npdu_data); if (reply.npdu_data.network_layer_message) { @@ -403,131 +281,344 @@ static bool dlmstp_compare_data_expecting_reply( return true; } -/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */ +/** + * @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(struct mstp_port_struct_t *mstp_port, unsigned timeout) -{ /* milliseconds to wait for a packet */ - uint16_t pdu_len = 0; /* return value */ - uint8_t destination = 0; /* destination address */ +{ + uint16_t pdu_len = 0; bool matched = false; + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; (void)timeout; - if (!Transmit_Packet.ready) { - return 0; - } - /* load destination MAC address */ - if (Transmit_Packet.address.mac_len == 1) { - destination = Transmit_Packet.address.mac[0]; - } else { - return 0; - } - if ((DLMSTP_HEADER_MAX + Transmit_Packet.pdu_len) > DLMSTP_MPDU_MAX) { + if (Ringbuf_Empty(&PDU_Queue)) { return 0; } + pkt = (struct mstp_pdu_packet *)Ringbuf_Peek(&PDU_Queue); /* is this the reply to the DER? */ matched = dlmstp_compare_data_expecting_reply( &mstp_port->InputBuffer[0], mstp_port->DataLength, - mstp_port->SourceAddress, &Transmit_Packet.pdu[0], - Transmit_Packet.pdu_len, &Transmit_Packet.address); + mstp_port->SourceAddress, (uint8_t *)&pkt->buffer[0], pkt->length, + pkt->destination_mac); if (!matched) { return 0; } + if (pkt->data_expecting_reply) { + frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + } else { + frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + } /* convert the PDU into the MSTP Frame */ pdu_len = MSTP_Create_Frame( &mstp_port->OutputBuffer[0], /* <-- loading this */ - mstp_port->OutputBufferSize, Transmit_Packet.frame_type, destination, - mstp_port->This_Station, &Transmit_Packet.pdu[0], - Transmit_Packet.pdu_len); - Transmit_Packet.ready = false; + mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + DLMSTP_Statistics.transmit_pdu_counter++; + (void)Ringbuf_Pop(&PDU_Queue, NULL); return pdu_len; } -void dlmstp_set_mac_address(uint8_t mac_address) +/** + * @brief Send an MSTP frame + * @param mstp_port - port specific data + * @param buffer - data to send + * @param nbytes - number of bytes of data to send + */ +void MSTP_Send_Frame( + struct mstp_port_struct_t *mstp_port, + const uint8_t *buffer, + uint16_t nbytes) { - /* Master Nodes can only have address 0-127 */ - if (mac_address <= 127) { - MSTP_Port.This_Station = mac_address; - /* FIXME: implement your data storage */ - /* I2C_Write_Byte( - EEPROM_DEVICE_ADDRESS, - mac_address, - EEPROM_MSTP_MAC_ADDR); */ - if (mac_address > MSTP_Port.Nmax_master) { - dlmstp_set_max_master(mac_address); - } - } - - return; + RS485_Send_Frame(mstp_port, buffer, nbytes); + DLMSTP_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(struct mstp_port_struct_t *mstp_port) +{ + uint16_t pdu_len = 0; + BOOL rc; + + WaitForSingleObject(Receive_Packet_Mutex, INFINITE); + if (!Receive_Packet.ready) { + /* bounds check - maybe this should send an abort? */ + pdu_len = mstp_port->DataLength; + if (pdu_len > sizeof(Receive_Packet.pdu)) { + pdu_len = sizeof(Receive_Packet.pdu); + } + memmove( + (void *)&Receive_Packet.pdu[0], (void *)&mstp_port->InputBuffer[0], + pdu_len); + dlmstp_fill_bacnet_address( + &Receive_Packet.address, mstp_port->SourceAddress); + Receive_Packet.pdu_len = mstp_port->DataLength; + Receive_Packet.ready = true; + rc = ReleaseSemaphore(Receive_Packet_Flag, 1, NULL); + (void)rc; + } + ReleaseMutex(Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * @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, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; + DWORD wait_status = 0; + + (void)max_pdu; + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + WaitForSingleObject(Receive_Packet_Mutex, INFINITE); + wait_status = WaitForSingleObject(Receive_Packet_Flag, timeout); + if (wait_status == WAIT_OBJECT_0) { + if (Receive_Packet.ready) { + if (Receive_Packet.pdu_len) { + DLMSTP_Statistics.receive_pdu_counter++; + if (src) { + memmove( + src, &Receive_Packet.address, + sizeof(Receive_Packet.address)); + } + if (pdu) { + memmove( + pdu, &Receive_Packet.pdu, sizeof(Receive_Packet.pdu)); + } + pdu_len = Receive_Packet.pdu_len; + } + Receive_Packet.ready = false; + } + } + ReleaseMutex(Receive_Packet_Mutex); + + return pdu_len; +} + +/** + * @brief Thread for the MS/TP receive state machine + * @param pArg not used + */ +static void dlmstp_thread(void *pArg) +{ + uint32_t silence_milliseconds = 0; + MSTP_MASTER_STATE master_state; + bool run_master = false; + + (void)pArg; + (void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + for (;;) { + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + /* note: RS485 waits up to 1ms for data to arrive */ + RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE) { + if (Preamble_Callback) { + Preamble_Callback(); + } + } + } + if (MSTP_Port.ReceivedValidFrame) { + DLMSTP_Statistics.receive_valid_frame_counter++; + if (Valid_Frame_Rx_Callback) { + Valid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else if (MSTP_Port.ReceivedInvalidFrame) { + if (Invalid_Frame_Rx_Callback) { + DLMSTP_Statistics.receive_invalid_frame_counter++; + Invalid_Frame_Rx_Callback( + MSTP_Port.SourceAddress, MSTP_Port.DestinationAddress, + MSTP_Port.FrameType, MSTP_Port.InputBuffer, + MSTP_Port.DataLength); + } + run_master = true; + } else { + silence_milliseconds = MSTP_Port.SilenceTimer(&MSTP_Port); + switch (MSTP_Port.master_state) { + case MSTP_MASTER_STATE_IDLE: + if (silence_milliseconds >= Tno_token) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (silence_milliseconds >= MSTP_Port.Treply_timeout) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (silence_milliseconds >= MSTP_Port.Tusage_timeout) { + run_master = true; + } + break; + default: + run_master = true; + break; + } + } + if (run_master) { + run_master = false; + if (MSTP_Port.SlaveNodeEnabled) { + MSTP_Slave_Node_FSM(&MSTP_Port); + } else { + if (MSTP_Port.ZeroConfigEnabled || MSTP_Port.CheckAutoBaud) { + /* if we are in auto baud or zero config mode, + we need to run the master state machine */ + } else if (MSTP_Port.This_Station > DEFAULT_MAX_MASTER) { + /* Master node address must be restricted */ + continue; + } + master_state = MSTP_Port.master_state; + while (MSTP_Master_Node_FSM(&MSTP_Port)) { + /* wait while some states fast transition */ + if (master_state != MSTP_Port.master_state) { + if (MSTP_Port.master_state == + MSTP_MASTER_STATE_NO_TOKEN) { + DLMSTP_Statistics.lost_token_counter++; + } + master_state = MSTP_Port.master_state; + } + } + } + } + } +} + +/** + * @brief Fill a BACnet address with the MSTP address + * @param src the BACnet address to fill + * @param mstp_address the MSTP MAC 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) +{ + MSTP_Port.This_Station = mac_address; +} + +/** + * @brief Get the MSTP MAC address + * @return MSTP MAC address + */ uint8_t dlmstp_mac_address(void) { return MSTP_Port.This_Station; } -/* 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. */ +/** + * @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) { MSTP_Port.Nmax_info_frames = max_info_frames; - /* FIXME: implement your data storage */ - /* I2C_Write_Byte( - EEPROM_DEVICE_ADDRESS, - (uint8_t)max_info_frames, - EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */ } return; } +/** + * @brief Get the MSTP max-info-frames value + * @return the MSTP max-info-frames value + */ uint8_t dlmstp_max_info_frames(void) { return MSTP_Port.Nmax_info_frames; } -/* 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. */ +/** + * @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; - /* FIXME: implement your data storage */ - /* I2C_Write_Byte( - EEPROM_DEVICE_ADDRESS, - max_master, - EEPROM_MSTP_MAX_MASTER_ADDR); */ - } + 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) { return MSTP_Port.Nmax_master; } -/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ -void dlmstp_set_baud_rate(uint32_t baud) -{ - RS485_Set_Baud_Rate(baud); -} - -uint32_t dlmstp_baud_rate(void) -{ - return RS485_Get_Baud_Rate(); -} - +/** + * @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 */ @@ -543,6 +634,10 @@ void dlmstp_get_my_address(BACNET_ADDRESS *my_address) 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 */ @@ -560,11 +655,265 @@ void dlmstp_get_broadcast_address(BACNET_ADDRESS *dest) return; } +/** + * @brief Get the MSTP port SoleMaster status + * @return true if the MSTP port is the SoleMaster + */ +bool dlmstp_sole_master(void) +{ + return MSTP_Port.SoleMaster; +} + +/** + * @brief Get the MSTP port SlaveNodeEnabled status + * @return true if the MSTP port has SlaveNodeEnabled + */ +bool dlmstp_slave_mode_enabled(void) +{ + return MSTP_Port.SlaveNodeEnabled; +} + +/** + * @brief Set the MSTP port SlaveNodeEnabled flag + * @param flag - true if the MSTP port has SlaveNodeEnabled + * @return true if the MSTP port SlaveNodeEnabled was set + * @note This flag is used to enable the Slave Node state machine + * for the MSTP port. The Slave Node state machine is used to + * respond to requests from the Master Node. + */ +bool dlmstp_slave_mode_enabled_set(bool flag) +{ + MSTP_Port.SlaveNodeEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port ZeroConfigEnabled status + * @return true if the MSTP port has ZeroConfigEnabled + */ +bool dlmstp_zero_config_enabled(void) +{ + return MSTP_Port.ZeroConfigEnabled; +} + +/** + * @brief Set the MSTP port ZeroConfigEnabled flag + * @param flag - true if the MSTP port has ZeroConfigEnabled + * @return true if the MSTP port ZeroConfigEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_zero_config_enabled_set(bool flag) +{ + MSTP_Port.ZeroConfigEnabled = flag; + + return true; +} + +/** + * @brief Get the MSTP port AutoBaudEnabled status + * @return true if the MSTP port has AutoBaudEnabled + */ +bool dlmstp_check_auto_baud(void) +{ + return MSTP_Port.CheckAutoBaud; +} + +/** + * @brief Set the MSTP port AutoBaudEnabled flag + * @param flag - true if the MSTP port has AutoBaudEnabled + * @return true if the MSTP port AutoBaudEnabled was set + * @note This flag is used to enable the Zero Configuration state machine + * for the MSTP port. The Zero Configuration state machine is used to + * automatically assign a MAC address to the MSTP port. + */ +bool dlmstp_check_auto_baud_set(bool flag) +{ + MSTP_Port.CheckAutoBaud = flag; + if (flag) { + MSTP_Port.Auto_Baud_State = MSTP_AUTO_BAUD_STATE_INIT; + } + + return true; +} + +/** + * @brief Get the MSTP port MAC address that this node prefers to use. + * @return ZeroConfigStation value, or an out-of-range value if invalid + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +uint8_t dlmstp_zero_config_preferred_station(void) +{ + return MSTP_Port.Zero_Config_Preferred_Station; +} + +/** + * @brief Set the MSTP port MAC address that this node prefers to use. + * @param station - Zero_Config_Preferred_Station value + * @return true if the MSTP port Zero_Config_Preferred_Station was set + * @note valid values are between Nmin_poll_station and Nmax_poll_station + * but other values such as 0 or 255 could mean 'unconfigured' + */ +bool dlmstp_zero_config_preferred_station_set(uint8_t station) +{ + MSTP_Port.Zero_Config_Preferred_Station = station; + + return true; +} + +/** + * @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) +{ + RS485_Set_Baud_Rate(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) +{ + return RS485_Get_Baud_Rate(); +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Valid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Frame Complete callback + * @param cb_func - callback function to be called when a frame is received + */ +void dlmstp_set_invalid_frame_rx_complete_callback( + dlmstp_hook_frame_rx_complete_cb cb_func) +{ + Invalid_Frame_Rx_Callback = cb_func; +} + +/** + * @brief Set the MS/TP Preamble callback + * @param cb_func - callback function to be called when a preamble is received + */ +void dlmstp_set_frame_rx_start_callback(dlmstp_hook_frame_rx_start_cb cb_func) +{ + Preamble_Callback = cb_func; +} + +/** + * @brief Reset the MS/TP statistics + */ +void dlmstp_reset_statistics(void) +{ + memset(&DLMSTP_Statistics, 0, sizeof(struct dlmstp_statistics)); +} + +/** + * @brief Copy the MSTP port statistics if they exist + * @param statistics - MSTP port statistics + */ +void dlmstp_fill_statistics(struct dlmstp_statistics *statistics) +{ + if (statistics == NULL) { + return; + } + memmove(statistics, &DLMSTP_Statistics, sizeof(struct dlmstp_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) +{ + (void)arg; + return mstimer_elapsed(&Silence_Timer); +} + +/** + * @brief Return the valid frame time in milliseconds + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +uint32_t dlmstp_valid_frame_milliseconds(void *arg) +{ + (void)arg; + return mstimer_elapsed(&Valid_Frame_Timer); +} + +/** + * @brief Reset the valid frame timer + * @param arg - pointer to MSTP port structure + * @return valid frame time in milliseconds + */ +void dlmstp_valid_frame_milliseconds_reset(void *arg) +{ + (void)arg; + mstimer_restart(&Valid_Frame_Timer); +} + +/** + * @brief Reset the RS-485 silence time to zero + * @param arg - pointer to MSTP port structure + */ +void dlmstp_silence_reset(void *arg) +{ + (void)arg; + mstimer_set(&Silence_Timer, 0); +} + +/** + * @brief Initialize this MS/TP datalink + * @param ifname user data structure + * @return true if the MSTP datalink is initialized + */ bool dlmstp_init(char *ifname) { unsigned long hThread = 0; uint32_t arg_value = 0; + /* Create a mutex with no initial owner, default security */ + Ring_Buffer_Mutex = CreateMutex(NULL, FALSE, "dlmstpRingBufferMutex"); + if (Ring_Buffer_Mutex == NULL) { + fprintf( + stderr, "MS/TP: CreateMutex error: %ld\n", (long)GetLastError()); + exit(1); + } + /* initialize PDU queue */ + Ringbuf_Init( + &PDU_Queue, (uint8_t *)&PDU_Buffer, sizeof(struct mstp_pdu_packet), + MSTP_PDU_PACKET_COUNT); /* initialize packet queue */ Receive_Packet.ready = false; Receive_Packet.pdu_len = 0; @@ -572,67 +921,62 @@ bool dlmstp_init(char *ifname) if (Receive_Packet_Flag == NULL) { exit(1); } - Received_Frame_Flag = CreateSemaphore(NULL, 0, 1, "dlsmtpReceiveFrame"); - if (Received_Frame_Flag == NULL) { - CloseHandle(Receive_Packet_Flag); + Receive_Packet_Mutex = CreateMutex(NULL, FALSE, "dlmstpReceivePacketMutex"); + if (Receive_Packet_Mutex == NULL) { + fprintf( + stderr, "MS/TP: CreateMutex error: %ld\n", (long)GetLastError()); exit(1); } /* 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); MSTP_Port.OutputBuffer = &TxBuffer[0]; MSTP_Port.OutputBufferSize = sizeof(TxBuffer); - MSTP_Port.SilenceTimer = Timer_Silence; - MSTP_Port.SilenceTimerReset = Timer_Silence_Reset; + MSTP_Port.SilenceTimer = dlmstp_silence_milliseconds; + MSTP_Port.SilenceTimerReset = dlmstp_silence_reset; + MSTP_Port.ValidFrameTimer = dlmstp_valid_frame_milliseconds; + MSTP_Port.ValidFrameTimerReset = dlmstp_valid_frame_milliseconds_reset; + MSTP_Port.BaudRate = dlmstp_baud_rate; + MSTP_Port.BaudRateSet = dlmstp_set_baud_rate; + /* always send reply postponed - can't meet timing on Windows */ + MSTP_Port.Treply_delay = 0; MSTP_Init(&MSTP_Port); -#if 0 - uint8_t data; - - /* FIXME: implement your data storage */ - data = 64; /* I2C_Read_Byte( - EEPROM_DEVICE_ADDRESS, - EEPROM_MSTP_MAC_ADDR); */ - if (data <= 127) - MSTP_Port.This_Station = data; - else - dlmstp_set_my_address(DEFAULT_MAC_ADDRESS); - /* FIXME: implement your data storage */ - data = 127; /* I2C_Read_Byte( - EEPROM_DEVICE_ADDRESS, - EEPROM_MSTP_MAX_MASTER_ADDR); */ - if ((data <= 127) && (data >= MSTP_Port.This_Station)) - MSTP_Port.Nmax_master = data; - else - dlmstp_set_max_master(DEFAULT_MAX_MASTER); - /* FIXME: implement your data storage */ - data = 1; - /* I2C_Read_Byte( - EEPROM_DEVICE_ADDRESS, - EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */ - if (data >= 1) - MSTP_Port.Nmax_info_frames = data; - else - dlmstp_set_max_info_frames(DEFAULT_MAX_INFO_FRAMES); -#endif #if PRINT_ENABLED fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station); fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master); fprintf( stderr, "MS/TP Max_Info_Frames: %u\n", (unsigned)MSTP_Port.Nmax_info_frames); + fprintf( + stderr, "MS/TP RxBuf[%u] TxBuf[%u]\n", + (unsigned)MSTP_Port.InputBufferSize, + (unsigned)MSTP_Port.OutputBufferSize); + fprintf( + stderr, + "MS/TP SlaveModeEnabled" + ": %s\n", + (MSTP_Port.SlaveNodeEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP ZeroConfigEnabled" + ": %s\n", + (MSTP_Port.ZeroConfigEnabled ? "true" : "false")); + fprintf( + stderr, + "MS/TP CheckAutoBaud" + ": %s\n", + (MSTP_Port.CheckAutoBaud ? "true" : "false")); + fflush(stderr); #endif - hThread = _beginthread(dlmstp_receive_fsm_task, 4096, &arg_value); + hThread = _beginthread(dlmstp_thread, 4096, &arg_value); if (hThread == 0) { - fprintf(stderr, "Failed to start recive FSM task\n"); - } - hThread = _beginthread(dlmstp_master_fsm_task, 4096, &arg_value); - if (hThread == 0) { - fprintf(stderr, "Failed to start Master Node FSM task\n"); + fprintf(stderr, "Failed to start MS/TP thread\n"); } return true; @@ -651,20 +995,6 @@ void apdu_handler( (void)pdu_len; } -/* returns a delta timestamp */ -uint32_t timestamp_ms(void) -{ - DWORD ticks = 0, delta_ticks = 0; - static DWORD last_ticks = 0; - - ticks = GetTickCount(); - delta_ticks = - (ticks >= last_ticks ? ticks - last_ticks : MAXDWORD - last_ticks); - last_ticks = ticks; - - return delta_ticks; -} - static char *Network_Interface = NULL; int main(int argc, char *argv[]) @@ -682,11 +1012,10 @@ int main(int argc, char *argv[]) dlmstp_init(Network_Interface); /* forever task */ for (;;) { - pdu_len = dlmstp_receive(NULL, NULL, 0, INFINITE); -#if 0 - MSTP_Create_And_Send_Frame(&MSTP_Port, FRAME_TYPE_TEST_REQUEST, - MSTP_Port.SourceAddress, MSTP_Port.This_Station, NULL, 0); -#endif + pdu_len = dlmstp_receive(NULL, NULL, 0, UINT_MAX); + MSTP_Create_And_Send_Frame( + &MSTP_Port, FRAME_TYPE_TEST_REQUEST, MSTP_Port.SourceAddress, + MSTP_Port.This_Station, NULL, 0); } return 0; diff --git a/ports/win32/rs485.c b/ports/win32/rs485.c index 954b1b37..97e471c4 100644 --- a/ports/win32/rs485.c +++ b/ports/win32/rs485.c @@ -187,7 +187,21 @@ static void RS485_Configure_Status(void) fprintf(stderr, "Unable to set status on %s\n", RS485_Port_Name); RS485_Print_Error(); } - /* configure the COM port timeout values */ + /* configure the time-out parameters for a communications device. */ + /* If an application sets ReadIntervalTimeout and + ReadTotalTimeoutMultiplier to MAXDWORD and + sets ReadTotalTimeoutConstant to a value greater + than zero and less than MAXDWORD, one of the following + occurs when the ReadFile function is called: + * If there are any bytes in the input buffer, + ReadFile returns immediately with the bytes in the buffer. + * If there are no bytes in the input buffer, + ReadFile waits until a byte arrives and then returns immediately. + * If no bytes arrive within the time specified + by ReadTotalTimeoutConstant, ReadFile times out. + + Constant values are in milliseconds + */ ctNew.ReadIntervalTimeout = MAXDWORD; ctNew.ReadTotalTimeoutMultiplier = MAXDWORD; ctNew.ReadTotalTimeoutConstant = 1; diff --git a/src/bacnet/basic/server/bacnet_port.c b/src/bacnet/basic/server/bacnet_port.c index 25f466fd..ba2cc136 100644 --- a/src/bacnet/basic/server/bacnet_port.c +++ b/src/bacnet/basic/server/bacnet_port.c @@ -45,6 +45,9 @@ void bacnet_port_task(void) bacnet_port_ipv4_task(elapsed_seconds); #elif defined(BACDL_BIP6) bacnet_port_ipv6_task(elapsed_seconds); +#else + /* nothing to do */ + (void)elapsed_seconds; #endif } } diff --git a/src/bacnet/config.h b/src/bacnet/config.h index 6dae7576..00e08c29 100644 --- a/src/bacnet/config.h +++ b/src/bacnet/config.h @@ -139,7 +139,7 @@ #define MAX_APDU 1476 #elif defined(BACDL_MSTP) && !defined(BACNET_SECURITY) /* note: MS/TP extended frames can be up to 1476 bytes */ -#define MAX_APDU 1476 +#define MAX_APDU 480 #elif defined(BACDL_ETHERNET) && !defined(BACNET_SECURITY) #define MAX_APDU 1476 #elif defined(BACDL_ETHERNET) && defined(BACNET_SECURITY) diff --git a/src/bacnet/datalink/dlenv.c b/src/bacnet/datalink/dlenv.c index 78f9925d..e1151f2d 100644 --- a/src/bacnet/datalink/dlenv.c +++ b/src/bacnet/datalink/dlenv.c @@ -34,6 +34,7 @@ /* enable debugging */ static bool Datalink_Debug; +static uint16_t Datalink_Debug_Timer_Seconds; /* timer used to renew Foreign Device Registration */ static uint16_t BBMD_Timer_Seconds; static uint16_t BBMD_TTL_Seconds = 60000; @@ -41,7 +42,9 @@ static uint16_t BBMD_TTL_Seconds = 60000; static BACNET_IP_ADDRESS BBMD_Address; static bool BBMD_Address_Valid; static uint16_t BBMD_Result = 0; +#if defined(BACDL_BIP) && BBMD_ENABLED static BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY BBMD_Table_Entry; +#endif static uint32_t Network_Port_Instance = 1; /** @@ -96,9 +99,12 @@ void dlenv_bbmd_ttl_set(uint16_t ttl_secs) */ int dlenv_bbmd_result(void) { - if ((BBMD_Result > 0) && - (bvlc_get_last_result() == BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK)) { - return -1; + if (BBMD_Result > 0) { +#if defined(BACDL_BIP) && BBMD_ENABLED + if (bvlc_get_last_result() == BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK) { + return -1; + } +#endif } /* Else, show our send: */ return BBMD_Result; @@ -439,6 +445,14 @@ void dlenv_network_port_mstp_init(uint32_t instance) if (pEnv) { mac_address = strtol(pEnv, NULL, 0); } + if (Datalink_Debug) { + fprintf( + stderr, + "Network Port[%lu] mode=MSTP bitrate=%ld mac[0]=%ld " + "max_info_frames=%ld, max_master=%ld\n", + (unsigned long)instance, baud_rate, mac_address, max_info_frames, + max_master); + } #ifdef BACDL_MSTP dlmstp_set_max_info_frames(max_info_frames); dlmstp_set_max_master(max_master); @@ -714,6 +728,10 @@ void dlenv_network_port_bsc_init(void) */ void dlenv_maintenance_timer(uint16_t elapsed_seconds) { +#ifdef BACDL_MSTP + struct dlmstp_statistics statistics = { 0 }; +#endif + if (BBMD_Timer_Seconds) { if (BBMD_Timer_Seconds <= elapsed_seconds) { BBMD_Timer_Seconds = 0; @@ -733,6 +751,27 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds) BBMD_Timer_Seconds = (uint16_t)BBMD_TTL_Seconds; } } + if (Network_Port_Type(Network_Port_Instance) == PORT_TYPE_MSTP) { + Datalink_Debug_Timer_Seconds = elapsed_seconds; + if (Datalink_Debug_Timer_Seconds >= 60) { + Datalink_Debug_Timer_Seconds = 0; + if (Datalink_Debug) { +#ifdef BACDL_MSTP + dlmstp_fill_statistics(&statistics); + fprintf( + stderr, + "MSTP: Frames Rx:%u/%u Tx:%u PDU Rx:%u Tx:%u Lost:%u\n", + statistics.receive_valid_frame_counter, + statistics.receive_invalid_frame_counter, + statistics.transmit_frame_counter, + statistics.transmit_pdu_counter, + statistics.receive_pdu_counter, + statistics.lost_token_counter); + fflush(stderr); +#endif + } + } + } } /** Initialize the DataLink configuration from Environment variables, @@ -838,7 +877,7 @@ void dlenv_init(void) port_type = PORT_TYPE_BIP; #elif defined(BACDL_BIP6) datalink_set("bip6"); - port_type = PORT_TYPE__BIP6; + port_type = PORT_TYPE_BIP6; #elif defined(BACDL_MSTP) datalink_set("mstp"); port_type = PORT_TYPE_MSTP; @@ -856,6 +895,24 @@ void dlenv_init(void) port_type = PORT_TYPE_NON_BACNET; #endif } +#else + /* if we are not compiling with multiple datalinks, + then we are using the only one available */ +#if defined(BACDL_BIP) + port_type = PORT_TYPE_BIP; +#elif defined(BACDL_BIP6) + port_type = PORT_TYPE_BIP6; +#elif defined(BACDL_MSTP) + port_type = PORT_TYPE_MSTP; +#elif defined(BACDL_ETHERNET) + port_type = PORT_TYPE_ETHERNET; +#elif defined(BACDL_ARCNET) + port_type = PORT_TYPE_ARCNET; +#elif defined(BACDL_BSC) + port_type = PORT_TYPE_BSC; +#else + port_type = PORT_TYPE_NON_BACNET; +#endif #endif Network_Port_Type_Set(Network_Port_Instance, port_type); switch (port_type) { @@ -888,7 +945,11 @@ void dlenv_init(void) apdu_retries_set((uint8_t)strtol(pEnv, NULL, 0)); } /* === Initialize the Datalink Here === */ - if (!datalink_init(getenv("BACNET_IFACE"))) { + pEnv = getenv("BACNET_IFACE"); + if (Datalink_Debug) { + fprintf(stderr, "BACNET_IFACE=%s\n", pEnv ? pEnv : "none"); + } + if (!datalink_init(pEnv)) { exit(1); } #if (MAX_TSM_TRANSACTIONS) diff --git a/src/bacnet/datalink/dlmstp.c b/src/bacnet/datalink/dlmstp.c index 8852d40b..1b9c9bfc 100644 --- a/src/bacnet/datalink/dlmstp.c +++ b/src/bacnet/datalink/dlmstp.c @@ -573,9 +573,7 @@ uint8_t dlmstp_max_info_frames(void) 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; - } + MSTP_Port->Nmax_master = max_master; } return; @@ -957,7 +955,8 @@ void dlmstp_fill_statistics(struct dlmstp_statistics *statistics) return; } if (statistics) { - *statistics = user->Statistics; + memmove( + statistics, &user->Statistics, sizeof(struct dlmstp_statistics)); } }