1
0
mirror of https://github.com/stargieg/bacnet-stack synced 2025-10-26 23:35:52 +08:00
This commit is contained in:
Patrick Grimm
2013-03-21 22:53:31 +01:00
parent 5668c55fca
commit 01349ec9e8
843 changed files with 190207 additions and 0 deletions

46
demo/mstpcap/Makefile Normal file
View File

@@ -0,0 +1,46 @@
#Makefile to build BACnet Application for the Linux Port
# tools - only if you need them.
# Most platforms have this already defined
# CC = gcc
# Executable file name
TARGET = mstpcap
TARGET_BIN = ${TARGET}$(TARGET_EXT)
# This demo seems to be a little unique
DEFINES = $(BACNET_DEFINES) -DBACDL_MSTP
BACNET_SOURCE_DIR = ../../src
SRCS = main.c \
${BACNET_PORT_DIR}/rs485.c \
${BACNET_PORT_DIR}/timer.c \
${BACNET_SOURCE_DIR}/fifo.c \
${BACNET_SOURCE_DIR}/mstp.c \
${BACNET_SOURCE_DIR}/mstptext.c \
${BACNET_SOURCE_DIR}/debug.c \
${BACNET_SOURCE_DIR}/indtext.c \
${BACNET_SOURCE_DIR}/ringbuf.c \
${BACNET_SOURCE_DIR}/crc.c
OBJS = ${SRCS:.c=.o}
all: Makefile ${TARGET_BIN}
${TARGET_BIN}: ${OBJS} Makefile
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
size $@
cp $@ ../../bin
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
clean:
rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map
include: .depend

889
demo/mstpcap/main.c Normal file
View File

@@ -0,0 +1,889 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2008 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307
USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
/* OS specific include*/
#include "net.h"
#include "timer.h"
/* local includes */
#include "bytes.h"
#include "rs485.h"
#include "crc.h"
#include "mstptext.h"
#include "version.h"
#include "dlmstp.h"
#ifndef max
#define max(a,b) (((a) (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
/* local port data - shared with RS-485 */
static volatile struct mstp_port_struct_t MSTP_Port;
/* buffers needed by mstp port struct */
static uint8_t RxBuffer[MAX_MPDU];
static uint8_t TxBuffer[MAX_MPDU];
/* statistics derived from monitoring the network for each node */
struct mstp_statistics {
/* counts how many times the node passes the token */
uint32_t token_count;
/* counts how many times the node receives the token */
uint32_t token_received_count;
/* counts how many times the node polls for another master */
uint32_t pfm_count;
/* counts how many times the node replies to polls from another master */
uint32_t rpfm_count;
/* counts how many times the node sends reply postponed */
uint32_t reply_postponed_count;
/* counts how many times the node sends a test request */
uint32_t test_request_count;
/* counts how many times the node sends a test response */
uint32_t test_response_count;
/* counts how many times the node sends a DER frame */
uint32_t der_count;
/* counts how many times the node sends a DNER frame */
uint32_t dner_count;
/* -- inferred data -- */
/* counts how many times the node gets a second token */
uint32_t token_retries;
/* delay after poll for master */
uint32_t tusage_timeout;
/* highest number MAC during poll for master */
uint8_t max_master;
/* highest number of frames sent during a token */
uint8_t max_info_frames;
/* how long it takes a node to pass the token */
uint32_t token_reply;
/* how long it takes a node to reply to PFM */
uint32_t pfm_reply;
/* how long it takes a node to reply to DER */
uint32_t der_reply;
/* how long it takes a node to send a reply post poned */
uint32_t reply_postponed;
/* number of tokens received before a Poll For Master cycle is executed */
uint32_t npoll;
/* number of total tokens at the last PFM */
uint32_t last_pfm_tokens;
/* Addendum 2008v - sending tokens to myself */
/* counts how many times the node passes the token */
uint32_t self_token_count;
};
static struct mstp_statistics MSTP_Statistics[256];
static uint32_t Invalid_Frame_Count;
static uint32_t timeval_diff_ms(
struct timeval *old,
struct timeval *now)
{
uint32_t ms = 0;
/* convert to milliseconds */
ms = (now->tv_sec - old->tv_sec) * 1000 + (now->tv_usec -
old->tv_usec) / 1000;
return ms;
}
static void packet_statistics(
struct timeval *tv,
volatile struct mstp_port_struct_t *mstp_port)
{
static struct timeval old_tv = { 0 };
static uint8_t old_frame = 255;
static uint8_t old_src = 255;
static uint8_t old_dst = 255;
uint8_t frame, src, dst;
uint32_t delta;
uint32_t npoll;
dst = mstp_port->DestinationAddress;
src = mstp_port->SourceAddress;
frame = mstp_port->FrameType;
switch (frame) {
case FRAME_TYPE_TOKEN:
MSTP_Statistics[src].token_count++;
MSTP_Statistics[dst].token_received_count++;
if (src == dst) {
MSTP_Statistics[src].self_token_count++;
}
if (old_frame == FRAME_TYPE_TOKEN) {
if ((old_dst == dst) && (old_src == src)) {
/* repeated token */
MSTP_Statistics[dst].token_retries++;
/* Tusage_timeout */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].tusage_timeout) {
MSTP_Statistics[src].tusage_timeout = delta;
}
} else if (old_dst == src) {
/* token to token response time */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].token_reply) {
MSTP_Statistics[src].token_reply = delta;
}
}
} else if ((old_frame == FRAME_TYPE_POLL_FOR_MASTER) &&
(old_src == src)) {
/* Tusage_timeout */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].tusage_timeout) {
MSTP_Statistics[src].tusage_timeout = delta;
}
}
break;
case FRAME_TYPE_POLL_FOR_MASTER:
if (MSTP_Statistics[src].last_pfm_tokens) {
npoll =
MSTP_Statistics[src].token_received_count -
MSTP_Statistics[src].last_pfm_tokens;
if (npoll > MSTP_Statistics[src].npoll) {
MSTP_Statistics[src].npoll = npoll;
}
}
MSTP_Statistics[src].last_pfm_tokens =
MSTP_Statistics[src].token_received_count;
MSTP_Statistics[src].pfm_count++;
if (dst > MSTP_Statistics[src].max_master) {
MSTP_Statistics[src].max_master = dst;
}
if ((old_frame == FRAME_TYPE_POLL_FOR_MASTER) && (old_src == src)) {
/* Tusage_timeout - sole master */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].tusage_timeout) {
MSTP_Statistics[src].tusage_timeout = delta;
}
}
break;
case FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER:
MSTP_Statistics[src].rpfm_count++;
if (old_frame == FRAME_TYPE_POLL_FOR_MASTER) {
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].pfm_reply) {
MSTP_Statistics[src].pfm_reply = delta;
}
}
break;
case FRAME_TYPE_TEST_REQUEST:
MSTP_Statistics[src].test_request_count++;
break;
case FRAME_TYPE_TEST_RESPONSE:
MSTP_Statistics[src].test_response_count++;
break;
case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY:
MSTP_Statistics[src].der_count++;
break;
case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY:
MSTP_Statistics[src].dner_count++;
if ((old_frame == FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY) &&
(old_dst == src)) {
/* DER response time */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].der_reply) {
MSTP_Statistics[src].der_reply = delta;
}
}
break;
case FRAME_TYPE_REPLY_POSTPONED:
MSTP_Statistics[src].reply_postponed_count++;
if ((old_frame == FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY) &&
(old_dst == src)) {
/* Postponed response time */
delta = timeval_diff_ms(&old_tv, tv);
if (delta > MSTP_Statistics[src].reply_postponed) {
MSTP_Statistics[src].reply_postponed = delta;
}
}
break;
default:
break;
}
/* update the old variables */
old_dst = dst;
old_src = src;
old_frame = frame;
old_tv.tv_sec = tv->tv_sec;
old_tv.tv_usec = tv->tv_usec;
}
static void packet_statistics_print(
void)
{
unsigned i; /* loop counter */
unsigned node_count = 0;
fprintf(stdout, "\r\n");
fprintf(stdout, "==== MS/TP Frame Counts ====\r\n");
fprintf(stdout, "%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s", "MAC", "Tokens",
"PFM", "RPFM", "DER", "Postpd", "DNER", "TestReq", "TestRsp");
fprintf(stdout, "\r\n");
for (i = 0; i < 256; i++) {
/* check for masters or slaves */
if ((MSTP_Statistics[i].token_count) || (MSTP_Statistics[i].der_reply)
|| (MSTP_Statistics[i].pfm_count)) {
node_count++;
fprintf(stdout, "%-8u", i);
fprintf(stdout, "%-8lu%-8lu%-8lu%-8lu",
(long unsigned int) MSTP_Statistics[i].token_count,
(long unsigned int) MSTP_Statistics[i].pfm_count,
(long unsigned int) MSTP_Statistics[i].rpfm_count,
(long unsigned int) MSTP_Statistics[i].der_count);
fprintf(stdout, "%-8lu%-8lu%-8lu%-8lu",
(long unsigned int) MSTP_Statistics[i].reply_postponed_count,
(long unsigned int) MSTP_Statistics[i].dner_count,
(long unsigned int) MSTP_Statistics[i].test_request_count,
(long unsigned int) MSTP_Statistics[i].test_response_count);
fprintf(stdout, "\r\n");
}
}
fprintf(stdout, "Node Count: %u\r\n", node_count);
node_count = 0;
fprintf(stdout, "\r\n");
fprintf(stdout, "==== MS/TP Usage and Timing Maximums ====\r\n");
fprintf(stdout, "%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-7s", "MAC",
"MaxMstr", "Retries", "Npoll", "Self", "Treply", "Tusage", "Trpfm",
"Tder", "Tpostpd");
fprintf(stdout, "\r\n");
for (i = 0; i < 256; i++) {
/* check for masters or slaves */
if ((MSTP_Statistics[i].token_count) || (MSTP_Statistics[i].der_reply)
|| (MSTP_Statistics[i].pfm_count)) {
node_count++;
fprintf(stdout, "%-8u", i);
fprintf(stdout, "%-8lu%-8lu%-8lu%-8lu%-8lu",
(long unsigned int) MSTP_Statistics[i].max_master,
(long unsigned int) MSTP_Statistics[i].token_retries,
(long unsigned int) MSTP_Statistics[i].npoll,
(long unsigned int) MSTP_Statistics[i].self_token_count,
(long unsigned int) MSTP_Statistics[i].token_reply);
fprintf(stdout, "%-8lu%-8lu%-8lu%-7lu",
(long unsigned int) MSTP_Statistics[i].tusage_timeout,
(long unsigned int) MSTP_Statistics[i].pfm_reply,
(long unsigned int) MSTP_Statistics[i].der_reply,
(long unsigned int) MSTP_Statistics[i].reply_postponed);
fprintf(stdout, "\r\n");
}
}
fprintf(stdout, "Node Count: %u\r\n", node_count);
fprintf(stdout, "Invalid Frame Count: %lu\r\n",
(long unsigned int) Invalid_Frame_Count);
}
static void packet_statistics_clear(
void)
{
memset(&MSTP_Statistics[0], 0, sizeof(MSTP_Statistics));
Invalid_Frame_Count = 0;
}
static uint32_t Timer_Silence(
void *pArg)
{
return timer_milliseconds(TIMER_SILENCE);
}
static void Timer_Silence_Reset(
void *pArg)
{
timer_reset(TIMER_SILENCE);
}
/* functions used by the MS/TP state machine to put or get data */
uint16_t MSTP_Put_Receive(
volatile struct mstp_port_struct_t *mstp_port)
{
(void) mstp_port;
return 0;
}
/* for the MS/TP state machine to use for getting data to send */
/* Return: amount of PDU data */
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
static char Capture_Filename[32] = "mstp_20090123091200.cap";
static FILE *pFile = NULL; /* stream pointer */
#if defined(_WIN32)
static HANDLE hPipe = NULL; /* pipe handle */
static void named_pipe_create(
char *name)
{
fprintf(stdout, "mstpcap: Creating Named Pipe \"%s\"\n", name);
hPipe =
CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND,
PIPE_TYPE_MESSAGE | PIPE_WAIT, 1, 65536, 65536, 300, NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
RS485_Print_Error();
return;
}
ConnectNamedPipe(hPipe, NULL);
}
size_t data_write(
const void *ptr,
size_t size,
size_t nitems)
{
DWORD cbWritten = 0;
if (hPipe != INVALID_HANDLE_VALUE) {
(void) WriteFile(hPipe, /* handle to pipe */
ptr, /* buffer to write from */
size * nitems, /* number of bytes to write */
&cbWritten, /* number of bytes written */
NULL); /* not overlapped I/O */
}
return fwrite(ptr, size, nitems, pFile);
}
size_t data_write_header(
const void *ptr,
size_t size,
size_t nitems,
bool pipe_enable)
{
DWORD cbWritten = 0;
if (pipe_enable && (hPipe != INVALID_HANDLE_VALUE)) {
(void) WriteFile(hPipe, /* handle to pipe */
ptr, /* buffer to write from */
size * nitems, /* number of bytes to write */
&cbWritten, /* number of bytes written */
NULL); /* not overlapped I/O */
}
return fwrite(ptr, size, nitems, pFile);
}
#else
static int FD_Pipe = -1;
static void named_pipe_create(
char *name)
{
int rv = 0;
rv = mkfifo(name, 0666);
if ((rv == -1) && (errno != EEXIST)) {
perror("Error creating named pipe");
exit(1);
}
FD_Pipe = open(name, O_WRONLY);
if (FD_Pipe == -1) {
perror("Error connecting to named pipe");
exit(1);
}
}
size_t data_write(
const void *ptr,
size_t size,
size_t nitems)
{
ssize_t bytes = 0;
if (FD_Pipe != -1) {
bytes = write(FD_Pipe, ptr, size * nitems);
bytes = bytes;
}
return fwrite(ptr, size, nitems, pFile);
}
size_t data_write_header(
const void *ptr,
size_t size,
size_t nitems,
bool pipe_enable)
{
ssize_t bytes = 0;
if (pipe_enable && (FD_Pipe != -1)) {
bytes = write(FD_Pipe, ptr, size * nitems);
bytes = bytes;
}
return fwrite(ptr, size, nitems, pFile);
}
#endif
static void filename_create(
char *filename)
{
time_t my_time;
struct tm *today;
if (filename) {
my_time = time(NULL);
today = localtime(&my_time);
sprintf(filename, "mstp_%04d%02d%02d%02d%02d%02d.cap",
1900 + today->tm_year, 1 + today->tm_mon, today->tm_mday,
today->tm_hour, today->tm_min, today->tm_sec);
}
}
/* write packet to file in libpcap format */
static void write_global_header(
const char *filename)
{
static bool pipe_enable = true; /* don't write more than one header */
uint32_t magic_number = 0xa1b2c3d4; /* magic number */
uint16_t version_major = 2; /* major version number */
uint16_t version_minor = 4; /* minor version number */
int32_t thiszone = 0; /* GMT to local correction */
uint32_t sigfigs = 0; /* accuracy of timestamps */
uint32_t snaplen = 65535; /* max length of captured packets, in octets */
uint32_t network = 165; /* data link type - BACNET_MS_TP */
/* create a new file. */
pFile = fopen(filename, "wb");
if (pFile) {
(void) data_write_header(&magic_number, sizeof(magic_number), 1,
pipe_enable);
(void) data_write_header(&version_major, sizeof(version_major), 1,
pipe_enable);
(void) data_write_header(&version_minor, sizeof(version_minor), 1,
pipe_enable);
(void) data_write_header(&thiszone, sizeof(thiszone), 1, pipe_enable);
(void) data_write_header(&sigfigs, sizeof(sigfigs), 1, pipe_enable);
(void) data_write_header(&snaplen, sizeof(snaplen), 1, pipe_enable);
(void) data_write_header(&network, sizeof(network), 1, pipe_enable);
fflush(pFile);
fprintf(stdout, "mstpcap: saving capture to %s\n", filename);
} else {
fprintf(stderr, "mstpcap[header]: failed to open %s: %s\n", filename,
strerror(errno));
}
if (pipe_enable) {
pipe_enable = false;
}
}
static void write_received_packet(
volatile struct mstp_port_struct_t *mstp_port)
{
uint32_t ts_sec; /* timestamp seconds */
uint32_t ts_usec; /* timestamp microseconds */
uint32_t incl_len; /* number of octets of packet saved in file */
uint32_t orig_len; /* actual length of packet */
uint8_t header[8]; /* MS/TP header */
struct timeval tv;
size_t max_data = 0;
if (pFile) {
gettimeofday(&tv, NULL);
ts_sec = tv.tv_sec;
ts_usec = tv.tv_usec;
if ((mstp_port->ReceivedValidFrame) ||
(mstp_port->ReceivedValidFrameNotForUs)) {
packet_statistics(&tv, mstp_port);
}
(void) data_write(&ts_sec, sizeof(ts_sec), 1);
(void) data_write(&ts_usec, sizeof(ts_usec), 1);
if (mstp_port->DataLength) {
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
incl_len = orig_len = 8 + max_data + 2;
} else {
incl_len = orig_len = 8;
}
(void) data_write(&incl_len, sizeof(incl_len), 1);
(void) data_write(&orig_len, sizeof(orig_len), 1);
header[0] = 0x55;
header[1] = 0xFF;
header[2] = mstp_port->FrameType;
header[3] = mstp_port->DestinationAddress;
header[4] = mstp_port->SourceAddress;
header[5] = HI_BYTE(mstp_port->DataLength);
header[6] = LO_BYTE(mstp_port->DataLength);
header[7] = mstp_port->HeaderCRCActual;
(void) data_write(header, sizeof(header), 1);
if (mstp_port->DataLength) {
(void) data_write(mstp_port->InputBuffer, max_data, 1);
(void) data_write((char *) &mstp_port->DataCRCActualMSB, 1, 1);
(void) data_write((char *) &mstp_port->DataCRCActualLSB, 1, 1);
}
} else {
fprintf(stderr, "mstpcap[packet]: failed to open %s: %s\n",
Capture_Filename, strerror(errno));
}
}
/* write packet to file in libpcap format */
static bool test_global_header(
const char *filename)
{
uint32_t magic_number = 0; /* magic number */
uint16_t version_major = 0; /* major version number */
uint16_t version_minor = 0; /* minor version number */
int32_t thiszone = 0; /* GMT to local correction */
uint32_t sigfigs = 0; /* accuracy of timestamps */
uint32_t snaplen = 0; /* max length of captured packets, in octets */
uint32_t network = 0; /* data link type - BACNET_MS_TP */
size_t count = 0;
/* create a new file. */
pFile = fopen(filename, "rb");
if (pFile) {
count = fread(&magic_number, sizeof(magic_number), 1, pFile);
if ((count != 1) || (magic_number != 0xa1b2c3d4)) {
fprintf(stderr, "mstpcap: invalid magic number\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&version_major, sizeof(version_major), 1, pFile);
if ((count != 1) || (version_major != 2)) {
fprintf(stderr, "mstpcap: invalid major version\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&version_minor, sizeof(version_minor), 1, pFile);
if ((count != 1) || (version_minor != 4)) {
fprintf(stderr, "mstpcap: invalid minor version\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&thiszone, sizeof(thiszone), 1, pFile);
if ((count != 1) || (thiszone != 0)) {
fprintf(stderr, "mstpcap: invalid time zone\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&sigfigs, sizeof(sigfigs), 1, pFile);
if ((count != 1) || (sigfigs != 0)) {
fprintf(stderr, "mstpcap: invalid time stamp accuracy\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&snaplen, sizeof(snaplen), 1, pFile);
if (count != 1) {
fprintf(stderr, "mstpcap: unable to read SNAP length\n");
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&network, sizeof(network), 1, pFile);
if ((count != 1) || (network != 165)) {
fprintf(stderr, "mstpcap: invalid data link type (DLT)\n");
fclose(pFile);
pFile = NULL;
return false;
}
} else {
fprintf(stderr, "mstpcap[scan]: failed to open %s: %s\n", filename,
strerror(errno));
return false;
}
return true;
}
static bool read_received_packet(
volatile struct mstp_port_struct_t *mstp_port)
{
uint32_t ts_sec = 0; /* timestamp seconds */
uint32_t ts_usec = 0; /* timestamp microseconds */
uint32_t incl_len = 0; /* number of octets of packet saved in file */
uint32_t orig_len = 0; /* actual length of packet */
uint8_t header[8] = { 0 }; /* MS/TP header */
struct timeval tv;
size_t count = 0;
if (pFile) {
count = fread(&ts_sec, sizeof(ts_sec), 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&ts_usec, sizeof(ts_usec), 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
tv.tv_sec = ts_sec;
tv.tv_usec = ts_usec;
count = fread(&incl_len, sizeof(incl_len), 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&orig_len, sizeof(orig_len), 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
count = fread(&header, sizeof(header), 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
mstp_port->FrameType = header[2];
mstp_port->DestinationAddress = header[3];
mstp_port->SourceAddress = header[4];
mstp_port->DataLength = MAKE_WORD(header[6], header[5]);
mstp_port->HeaderCRCActual = header[7];
if (orig_len > 8) {
mstp_port->DataLength = orig_len - 8 - 2;
count =
fread(mstp_port->InputBuffer, mstp_port->DataLength, 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
count = fread((char *) &mstp_port->DataCRCActualMSB, 1, 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
count = fread((char *) &mstp_port->DataCRCActualLSB, 1, 1, pFile);
if (count != 1) {
fclose(pFile);
pFile = NULL;
return false;
}
} else {
mstp_port->DataLength = 0;
}
packet_statistics(&tv, mstp_port);
} else {
return false;
}
return true;
}
static void cleanup(
void)
{
packet_statistics_print();
if (pFile) {
fflush(pFile); /* stream pointer */
fclose(pFile); /* stream pointer */
}
pFile = NULL;
}
#if defined(_WIN32)
static BOOL WINAPI CtrlCHandler(
DWORD dwCtrlType)
{
dwCtrlType = dwCtrlType;
if (hPipe) {
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
exit(0);
return TRUE;
}
#else
static void sig_int(
int signo)
{
(void) signo;
if (FD_Pipe != -1) {
close(FD_Pipe);
}
exit(0);
}
void signal_init(
void)
{
signal(SIGINT, sig_int);
signal(SIGHUP, sig_int);
signal(SIGTERM, sig_int);
}
#endif
void filename_create_new(
void)
{
if (pFile) {
fclose(pFile);
}
pFile = NULL;
filename_create(&Capture_Filename[0]);
write_global_header(&Capture_Filename[0]);
}
/* simple test to packetize the data and print it */
int main(
int argc,
char *argv[])
{
volatile struct mstp_port_struct_t *mstp_port;
long my_baud = 38400;
uint32_t packet_count = 0;
MSTP_Port.InputBuffer = &RxBuffer[0];
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
MSTP_Port.OutputBuffer = &TxBuffer[0];
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
MSTP_Port.This_Station = 127;
MSTP_Port.Nmax_info_frames = 1;
MSTP_Port.Nmax_master = 127;
MSTP_Port.SilenceTimer = Timer_Silence;
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
/* mimic our pointer in the state machine */
mstp_port = &MSTP_Port;
MSTP_Init(mstp_port);
/* initialize our interface */
if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) {
printf("mstpcap --scan <filename>\r\n"
"perform statistic analysis on MS/TP capture file.\r\n");
printf("\r\n");
printf("mstpcap [interface] [baud] [named pipe]\r\n"
"Captures MS/TP packets from a serial interface\r\n"
"and save them to a file. Saves packets in a\r\n"
"filename mstp_20090123091200.cap that has data and time.\r\n"
"After receiving 65535 packets, a new file is created.\r\n" "\r\n"
"Command line options:\r\n" "[interface] - serial interface.\r\n"
" defaults to COM4 on Windows, and /dev/ttyUSB0 on linux.\r\n"
"[baud] - baud rate. 9600, 19200, 38400, 57600, 115200\r\n"
" defaults to 38400.\r\n"
"[named pipe] - use \\\\.\\pipe\\wireshark as the name\r\n"
" and set that name as the interface name in Wireshark\r\n");
return 0;
}
if ((argc > 1) && (strcmp(argv[1], "--version") == 0)) {
printf("mstpcap %s\r\n", BACNET_VERSION_TEXT);
printf("Copyright (C) 2011 by Steve Karg\r\n"
"This is free software; see the source for copying conditions.\r\n"
"There is NO warranty; not even for MERCHANTABILITY or\r\n"
"FITNESS FOR A PARTICULAR PURPOSE.\r\n");
return 0;
}
if ((argc > 1) && (strcmp(argv[1], "--scan") == 0)) {
if (argc > 2) {
printf("Scanning %s\r\n", argv[2]);
/* perform statistics on the file */
if (test_global_header(argv[2])) {
while (read_received_packet(mstp_port)) {
packet_count++;
fprintf(stderr, "\r%u packets", (unsigned) packet_count);
}
if (packet_count) {
packet_statistics_print();
}
} else {
fprintf(stderr, "File header does not match.\n");
}
return 1;
}
}
if (argc > 1) {
RS485_Set_Interface(argv[1]);
}
if (argc > 2) {
my_baud = strtol(argv[2], NULL, 0);
RS485_Set_Baud_Rate(my_baud);
}
atexit(cleanup);
RS485_Initialize();
timer_init();
fprintf(stdout, "mstpcap: Using %s for capture at %ld bps.\n",
RS485_Interface(), (long) RS485_Get_Baud_Rate());
#if defined(_WIN32)
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlCHandler, TRUE);
#else
signal_init();
#endif
if (argc > 3) {
named_pipe_create(argv[3]);
}
filename_create_new();
/* run forever */
for (;;) {
RS485_Check_UART_Data(mstp_port);
MSTP_Receive_Frame_FSM(mstp_port);
/* process the data portion of the frame */
if (mstp_port->ReceivedValidFrame) {
write_received_packet(mstp_port);
mstp_port->ReceivedValidFrame = false;
packet_count++;
} else if (mstp_port->ReceivedValidFrameNotForUs) {
write_received_packet(mstp_port);
mstp_port->ReceivedValidFrameNotForUs = false;
packet_count++;
} else if (mstp_port->ReceivedInvalidFrame) {
write_received_packet(mstp_port);
Invalid_Frame_Count++;
mstp_port->ReceivedInvalidFrame = false;
packet_count++;
}
if (!(packet_count % 100)) {
fprintf(stdout, "\r%hu packets, %hu invalid frames", packet_count,
Invalid_Frame_Count);
}
if (packet_count >= 65535) {
packet_statistics_print();
packet_statistics_clear();
filename_create_new();
packet_count = 0;
}
}
}

139
demo/mstpcap/makefile.b32 Normal file
View File

@@ -0,0 +1,139 @@
#
# Simple makefile to build an executable for Win32
#
# This makefile assumes Borland bcc32 development environment
# on Windows NT/9x/2000/XP
#
!ifndef BORLAND_DIR
BORLAND_DIR_Not_Defined:
@echo .
@echo You must define environment variable BORLAND_DIR to compile.
!endif
# target
PRODUCT = mstpcap
PRODUCT_EXE = $(PRODUCT).exe
# tools
CC = $(BORLAND_DIR)\bin\bcc32
MAKE=$(BORLAND_DIR)\bin\make.exe
#LINK = $(BORLAND_DIR)\bin\tlink32
LINK = $(BORLAND_DIR)\bin\ilink32
BACNET_LIB_DIR = ..\..\lib
BACNET_LIB = $(BACNET_LIB_DIR)\bacnet.lib
# directories
BACNET_PORT = ..\..\ports\win32
BACNET_INCLUDE = ..\..\include
BACNET_OBJECT = ..\object
BACNET_HANDLER = ..\handler
INCLUDES = \
-I$(BACNET_INCLUDE) \
-I$(BACNET_PORT) \
-I$(BACNET_OBJECT) \
-I$(BACNET_HANDLER) \
-I$(BORLAND_DIR)\include
#
BACNET_DEFINES = -DPRINT_ENABLED=1 -DBACAPP_ALL
#BACDL_DEFINE=-DBACDL_MSTP=1
BACDL_DEFINE=-DBACDL_BIP=1 -DUSE_INADDR=1
DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE)
SRCS = main.c \
OBJS = $(SRCS:.c=.obj)
#
# Compiler definitions
#
BCC_CFG = bcc32.cfg
#
# Include directories
#
CFLAGS = $(INCLUDES) $(DEFINES)
#
# Libraries
#
C_LIB_DIR = $(BORLAND_DIR)\lib
LIBS = $(BACNET_LIB) \
$(C_LIB_DIR)\IMPORT32.lib \
$(C_LIB_DIR)\CW32MT.lib \
#
# Main target
#
# This should be the first one in the makefile
all : $(BACNET_LIB) $(BCC_CFG) $(OBJS) $(PRODUCT_EXE)
del $(BCC_CFG)
install: $(PRODUCT_EXE)
copy $(PRODUCT_EXE) ..\..\bin\$(PRODUCT_EXE)
# Linker specific: the link below is for BCC linker/compiler. If you link
# with a different linker - please change accordingly.
#
# need a temp response file (@&&| ... |) because command line is too long
# $** lists each dependency
# $< target name
# $* target name without extension
$(PRODUCT_EXE) : $(OBJS)
@echo Running Linker for $(PRODUCT_EXE)
$(LINK) -L$(C_LIB_DIR) -L$(BACNET_LIB_DIR) -m -c -s -v @&&|
$(BORLAND_DIR)\lib\c0x32.obj $**
$<
$*.map
$(LIBS)
|
#
# Utilities
clean :
del $(OBJS)
del $(PRODUCT_EXE)
del $(PRODUCT).map
del $(PRODUCT).ilc
del $(PRODUCT).ild
del $(PRODUCT).ilf
del $(PRODUCT).ils
del $(PRODUCT).tds
del $(BCC_CFG)
#
# Generic rules
#
.SUFFIXES: .cpp .c .sbr .obj
#
# cc generic rule
#
.c.obj:
$(CC) +$(BCC_CFG) -o$@ $<
# Compiler configuration file
$(BCC_CFG) :
Copy &&|
$(CFLAGS)
-c
-y #include line numbers in OBJ's
-v #include debug info
-w+ #turn on all warnings
-Od #disable all optimizations
#-a4 #32 bit data alignment
#-M # generate link map
#-ls # linker options
#-WM- #not multithread
-WM #multithread
-w-aus # ignore warning assigned a value that is never used
-w-sig # ignore warning conversion may lose sig digits
| $@
# EOF: makefile

177
demo/mstpcap/mstpcap.txt Normal file
View File

@@ -0,0 +1,177 @@
BACnet MS/TP Capture Tool
This tool captures BACnet MS/TP packets on an RS485 serial interface,
and saves the packets to a file in Wireshark PCAP format for
the BACnet MS/TP dissector to read. The filename has a date and time
code in it, and will contain up to 65535 packets. A new file
will be created at each 65535 packet interval. The tool can
be stopped by using Control-C.
Here is a sample of the tool running (use CTRL-C to quit):
D:\code\bacnet-stack>bin\mstpcap.exe com54 38400
Adjusted interface name to \\.\COM54
mstpcap: Using \\.\COM54 for capture at 38400 bps.
mstpcap: saving capture to mstp_20110413134119.cap
1156 packets
==== MS/TP Frame Counts ====
MAC Tokens PFM RPFM DER Postpd DNER TestReq TestRsp
0 188 4 0 0 0 0 0 0
2 189 0 0 0 0 0 0 0
3 189 9 0 0 0 0 0 0
7 189 60 0 0 0 0 0 0
35 188 140 0 0 0 0 0 0
Node Count: 5
==== MS/TP Usage and Timing Maximums ====
MAC MaxMstr Retries Npoll Self Treply Tusage Trpfm Tder Tpostpd
0 1 0 52 0 11 24 0 0 0
2 0 0 0 0 23 0 0 0 0
3 6 0 50 0 5 100 0 0 0
7 34 0 52 0 5 34 0 0 0
35 127 0 50 0 6 63 0 0 0
Node Count: 5
Invalid Frame Count: 0
The files that are captured can also be scanned to give some statistics:
D:\code\bacnet-stack>bin\mstpcap.exe --scan mstp_20110413134119.cap
Scanning mstp_20110413134119.cap
1156 packets
==== MS/TP Frame Counts ====
MAC Tokens PFM RPFM DER Postpd DNER TestReq TestRsp
0 188 4 0 0 0 0 0 0
2 189 0 0 0 0 0 0 0
3 189 9 0 0 0 0 0 0
7 189 60 0 0 0 0 0 0
35 188 140 0 0 0 0 0 0
Node Count: 5
==== MS/TP Usage and Timing Maximums ====
MAC MaxMstr Retries Npoll Self Treply Tusage Trpfm Tder Tpostpd
0 1 0 52 0 11 24 0 0 0
2 0 0 0 0 23 0 0 0 0
3 6 0 50 0 5 100 0 0 0
7 34 0 52 0 5 34 0 0 0
35 127 0 50 0 6 63 0 0 0
Node Count: 5
Invalid Frame Count: 0
The BACnet MS/TP capture tool also includes statistics which are
listed for any MAC addresses found passing a token,
or any MAC address replying to a DER message.
The statistics are emitted when Control-C is pressed, or when
65535 packets are captured and the new file is created.
The statistics are cleared when the new file is created.
The statistics can be emitted from a file using the "--scan" option.
The MS/TP Frame counts use the following abbreviations:
Tokens = number of Token frames sent from this MAC address.
PFM = number of Poll-For-Master frames sent from this MAC address.
RPFM = number of Reply-To-Poll-For-Master frames sent from this MAC address.
DER = number of Data-Expecting-Reply frames sent from this MAC address.
Postpd = number of Reply-Postponed frames sent from this MAC address.
DNER = number of Data-Not-Expecting-Reply frames sent from this MAC address.
TestReq = number of Test-Request frames sent from this MAC address.
TestRsp = number of Test-Response frames sent from this MAC address.
The MS/TP Usage and Timing Maximums use the following abbreviations:
MaxMstr = highest destination MAC address during PFM
Retries = number of second tokens sent to this MAC address.
Npoll = number of Tokens between Poll-For-Master
Self = number of Tokens sent to self (Addendum 135-2008v)
Treply = maximum number of milliseconds it took to reply with
a token after receiving a token. Treply is required to be less
than 25ms (but the mstpcap tool may not have that good of
resolution on Windows).
Tusage = the maximum number of milliseconds the
device waits for a ReplyToPollForMaster or Token retry.
Tusage is required to be between 20ms and 100ms.
Trpfm = maximum number of milliseconds to respond to PFM with RPFM. It is
required to be less than 25ms.
Tder = maximum number of milliseconds that a device takes to
respond to a DataExpectingReply request. Tder is required to be less
than 250ms.
Tpostpd = maximum number of milliseconds to respond to
DataExpectingReply request with ReplyPostponed. Tpostpd is
required to be less than 250ms.
==== FTDI chip RS-485 converter 76800 baud tricks ====
If you are using FTDI chip in your RS485 converter, you can
alias an existing baud rate on Windows in the FTDIPORT.INF file
in order to acheive non-standard 76800 bps:
HKR,,"ConfigData",1,11,00,3F,3F,27,C0,00,00,27,00,00,00,C4,09,00,00,E2,04,00,00,71,02,00,00,38,41,00,00,9C,80,00,00,4E,C0,00,00,34,00,00,00,1A,00,00,00,0D,00,00,00,06,40,00,00,03,80,00,00,00,00,00,00,D0,80,00,00
replace the 10,27,00,00 => divisor = 10000, rate = 300 bps alias
hex values actual
----------- ---------
27,C0,00,00 - 76923 bps => divisor=39.125
27,00,00,00 - 76677 bps => divisor=39.000
Windows XP (from koby3101)
1) Plug in and locate your USB/RSS85 in Device Manager under ports. Right click
on it and select Properties. Click Details tab and from the drop down select
Device Instance Id.
2) Click Start, Run and then type regedit.
Follow this path in the registry
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\FTDIBUS
Locate the folder that has the same name as what you found earlier Device Instance
Id in step 1. Click on 0000 folder and then Device Parameters. On the right
side you will see ConfigData. Right click and select Modify Binary Data.
Locate the 10 27 which in my case were in 5th and 6th position and replace with
27 C0.
This will make the RS485 go to 76800 baud (76923 baud) baud when you ask it
to be 300 baud.
So to capture at 76800 baud type: mstpcap.exe COM2 300
Linux (used with Debian Lenny and Fedora 15)
http://www.connecttech.com/KnowledgeDatabase/kdb309.htm
As root:
Change USB so I can use it later as normal user:
# chmod 777 /dev/ttyUSB0 -
Print current info about the device:
# setserial /dev/ttyUSB0 <20>a
/dev/ttyUSB0, Line 0, UART: unknown, Port: 0x0000, IRQ: 0
Baud_base: 24000000, close_delay: 0, divisor: 0
closing_wait: infinte
Flags: spd_normal low_latency
Make custom speed:
# setserial /dev/ttyUSB0 spd_cust
24000000/312 gives 76923 baudrate:
# setserial /dev/ttyUSB0 divisor 312
Print to make sure changes got applied:
# setserial /dev/ttyUSB0 <20>a
/dev/ttyUSB0, Line 0, UART: unknown, Port: 0x0000, IRQ: 0
Baud_base: 24000000, close_delay: 0, divisor: 312
closing_wait: infinte
Flags: spd_cust low_latency
Now as normal user running the mstpcap which uses the default 38400 baud it
will actually capture at 76800 baud. (76923)
Just navigate (cd bin) to bin folder in the project and type:
$ ./mstpcap